JS异步编程

categories: [前端,JavaScript,异步编程]
thumbnail: /images/fe/yibu.jpg
toc: true

序言

在event loop中我们已经学习了,js是一门单线程语言,这意味着通常情况下JS运行的代码是同步且阻塞的,但是在实际应用中,尤其是在浏览器端,这种同步阻塞的编程无法实现特定的需求,当遇到一些耗时的计算,请求时就会造成后续线程的等待甚至卡死,给用户造成非常糟糕的体验。

基于这个问题,JS的异步编程解决方案应运而生。时至今日,前端开发人员依然能听到一句话:JS是单线程的,天生异步,适合IO密集型,不适合CPU密集型

接下来就来了解JS异步编程的实现方案

回调函数

一提到回调函数所有人应该都不陌生,从第一天学习JS开始,我们就了解到JS的一个全局函数:setTimeout。

setTimeout(function () {
   
    console.log('Time out');
}, 1000);

在event loop这篇文章中,我们重新认识了setTimeout,它不再是一个简简单单的让代码延迟执行的定时器函数,同时也是JS实现异步编程的一个工具之一
上述例子中,setTimeout内部的这个匿名函数,就叫做setTimeout的回调函数,意义就是在1秒之后,执行回调函数

对于回调函数,大多数人的第一反应应该还是ajax的回调,在请求完成之后执行一段代码。这样看来,回调函数似乎还蛮好用的,能够手动掌握一些代码的执行顺序,但是,考虑一下下面这个情况:

回调地狱


这是微信小程序官方推荐(规定)的登录流程,可以看到,对于前端来说,需要先调用wx.login()接口获取code,然后使用code向自己的后台请求session_key,之后再调用其他业务接口时带上这个session_key,后台再返回数据。

对于上述流程,在使用axaj回调函数时,代码是这样的

wx.login({
   
 success(res) {
   
 //第一层回调,调用wx.login接口
    if (res.code) {
   
      ajax.request({
   
        method: 'POST',
        url: 'user/register',
        data: {
   
          code: res.code,
        },
        success: result => {
   
          //第二层回调,获取session_key
          ajax.request({
   
	        method: 'POST',
	        url: 'user/getinfo',
	        data: {
   
	          session_key: res.session_key,
	        },
	        success: result => {
   
	          //第三层回调,获取业务数据
	          ...
	        },
	      })
        },
      })
    }
  }
})

可以看到,整段代码充满了回调嵌套,代码不仅在纵向扩展,横向也在扩展。我相信,对于任何人来说,调试起来都会很困难,我们不得不从一个函数跳到下一个,再跳到下一个,在整个代码中跳来跳去以查看流程,而最终的结果藏在整段代码的中间位置。真实的JavaScript程序代码可能要混乱的多,使得这种追踪难度会成倍增加。这就是我们常说的回调地狱(Callback Hell)

为什么会出现这种现象?

如果某个业务,依赖于上层业务的数据,上层业务又依赖于更上一层的数据,我们还采用回调的方式来处理异步的话,就会出现回调地狱。

大脑对于事情的计划方式是线性的、阻塞的、单线程的语义,但是回调表达异步流程的方式是非线性的、非顺序的,这使得正确推导这样的代码的难度很大,很容易产生Bug。

控制反转

// A
$.ajax({
   
    ...
    success: function (...) {
   
        // C
    }
});
// B

对于上述代码,代码A和代码B是同步代码,由JavaScript的event loop机制监督执行,换句话说,JS扮演了一个绝对可靠的执行者,我们将代码交给它去执行。而代码C,却是由ajax库提供的回调API来执行,这个第三方的身份相对于JS来说就不是那么可靠。

ajax大家都知道是JS提供的异步请求方法与标准,这个问题在这个例子上貌似不会有太严重,但是,请不要被这个小概率迷惑而认为这种控制切换不是什么大问题。实际上,这是回调驱动设计最严重(也是最微妙)的问题。它以这样一个思路为中心:有时候ajax(…),也就是你交付回调函数的第三方不是你编写的代码,也不在你的直接控制之下,它是某个第三方提供的工具。

这种情况称为控制反转,也就是把自己程序一部分的执行控制交给某个第三方,在你的代码和第三方工具直接有一份并没有明确表达的契约。

既然是无法控制的第三方在执行你的回调函数,那么就有可能存在以下问题,当然通常情况下是不会发生的:

  • 调用回调过早
  • 调用回调过晚
  • 调用回调次数太多或者太少
  • 未能把所需的参数成功传给你的回调函数
  • 吞掉可能出现的错误或异常

这种控制反转会导致信任链的完全断裂,如果你没有采取行动来解决这些控制反转导致的信任问题,那么你的代码已经有了隐藏的Bug,尽管我们大多数人都没有这样做。

Promise

开门见山,Promise解决的是回调函数处理异步的第2个问题:控制反转
在event loop中我们知道了,对于异步代码,event loop会将其放入event queue(任务队列)当中。而任务队列又分为宏队列和微队列,Promise就被分在了微队列当中。

由此可见,使用Promise之后代码的运行将由event loop完全接管,由Javascript运行机制来运行

Promise的含义

所谓Promisem,简单来说就是一个容器,里面保存着某个未来才会结束的事件的结果,说白了就是一个耗时操作。从语法上来说,Promise是一个对象,他可以获取异步操作的消息,Promise提供统一的API,各种异步操作可以用同样的方法进行处理。

Promise对象有两个特点:

  • 对象的状态不受外界影响。Promise有三种状态:Pending(进行中), Fulfilled(已成功), Rejected(已失败),只有异步操作的结果可以改变这个状态,其他手段无法改变
  • 一旦状态改变之后就是稳定下来,不会发生二次改变。从pending变为Fulfilled或Rejected后,就一直保持这个结果,称为Resolve(已定型)。

    Promise可以将异步操作用同步的流程表达出来,避免了层层嵌套

用法

Promise是一个构造函数 ,可以生成实例对象

var promise = new Promise(function(resolve, reject){
   
	//....一些代码
	if(//异步操作成功){
   
		resolve(value)
	}
	else{
   
		reject(err)
	}
})

Promise构造函数接收一个函数作为参数,这个函数接收两个参数:resolve和reject。resolve的作用是将状态从pending变为Resolve,并将异步操作的结果带出去,异步操作成功时调用。reject的作用是将状态从pending变为Reject, 异步操作失败时调用,并将错误传递出去。

Promise实例生成后可以指定then方法作为回调,then方法接收两个函数作为参数, 第一个是promise状态变为resolved时调用,第二个是状态变为rejected时调用,其中,第二个参数是可选的

一个简单的例子

let propmise = new Promise(function(resolve, reject){
   
	console.log('a')
	resolve()
})

promise.then(function{
   
	console.log('b')
})

console.log('c')

//结果:acb

链式调用

我们把上面那个多层回调嵌套的例子用Promise的方式重构

//获取code
let getKeyPromise = function () {
   
    return new Promsie(function (resolve, reject) {
   
       wx.login({
   
		 success(res) {
   
		 //第一层回调,调用wx.login接口
		    if (res.code) {
   
		    	resolve(res.code)
		    }
		  }
		})
    });
};
//获取session_ket
let getTokenPromise = function (key) {
   
    return new Promsie(function (resolve, reject) {
   
        $.ajax({
   
            type: 'get',
            url: 'http://localhost:3000/getToken',
            data: {
   
                code: key
            },
            success: function (res) {
   
                resolve(res.session_key);         
            },
            error: function (err) {
   
                reject(err);
            }
        });
    });
};
//获取业务数据
let getDataPromise = function (data) {
   
    return new Promsie(function (resolve, reject) {
   
        $.ajax({
   
            type: 'get',
            url: 'http://localhost:3000/getData',
            data: {
   
                session_key: data,
            },
            success: function (res) {
   
                
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值