
1.7 异步编程
1.7.1 ES5中的异步
JavaScript引擎是基于事件循环的概念实现的,JavaScript引擎会把任务放在一个任务队列中,通过事件循环机制一一执行任务队列里的任务,从第一个依次执行到最后一个。有些任务执行可能时间会比较长,如果等待时间比较长的任务执行完成之后再执行下一个任务就会影响用户体验,所以JavaScript在设计的时候就有了同步和异步。异步任务不进入主线程,而进入任务队列中的任务,只有任务队列通知主线程,某个异步任务可以执行了,这个任务才会进入主线程执行。异步任务在ES5标准中通过回调来解决执行顺序问题,代码如下:

上述代码通过setTimeout模拟异步过程,想要异步逻辑执行完成之后再执行"执行完之后的回调打印...",可以通过回调的方式来实现,把function(){}作为cb参数传入异步逻辑中,在异步逻辑执行完成之后再执行回调函数,这样就可以实现执行之后执行打印逻辑。这种写法可以通过回调来控制异步执行问题,但是如果回调过多就会出现“回调地狱”的情况。如下:


上述代码回调过多,如果逻辑比较复杂,会导致后期再更新维护的时候变得复杂,所以针对这种情况,ES6标准中出现Promise来解决异步问题,可以将回调的写法变得更加简洁。
1.7.2 Promise基本语法
首先Promise是系统中预定义的类,通过实例化可以得到Promise对象。Promise对象会有三种状态,分别是pending、resolved、rejected,代码如下:

上述代码中Promise回调函数里如果没有调取resolve或者reject,那么就会返还一个Pending状态的Promise对象。如果调取了resolve函数就会返还一个resolved状态的Promise对象(在火狐上略有不同,会显示fullfilled状态的Promise对象)。如果调取了reject函数则会返还一个rejected状态的对象。每一个Promise对象都会有一个then方法,then方法里会接收两个参数(可选),代码如下:


上述代码中如果调用resolve函数,在执行then的时候会执行到第一个成功的回调中去,如果调取reject函数则会执行到then的第二个错误的回调中去。当然Promise也提供catch方法来捕捉reject错误,代码如下:

使用catch的好处是如果有多个then,会把最先报错的错误抛出到catch里面。这样在写法上更加简单。调用then函数之后会有三种返还值。
1)then里没有返还值,会默认返还一个Promise对象。
2)then里如果有返还值会将返还值包装成一个Promise对象返还。
3)如果返还的是Promise对象,then函数也会直接返还原本的Promise对象。代码如下:


1.7.3 Promise处理异步问题
了解Promise基本用法之后就可以通过Promise来处理异步问题了,在写法上可以理解为,通过Promise里提供的resolve及reject替换原本处理异步的回调函数。这样可以使用Promise对象提供的then方法,由于每个then方法又会返还一个Promise对象,所以就可以实现then的链式调用,从而解决回调地狱的问题。代码如下:

涉及多个异步逻辑的时候就可以使用then的链式操作,代码如下:


通过上述代码可以看出通过Promise及then的返还特性将异步改写成then的链式操作,这样就避免了回调地狱的情况,写法上也变得更加优雅。在ES7标准中新增了async及await使上述Promise变得更加简单和易用,如果不考虑兼容性,或者有自动化工具的情况下,建议使用async和await写法,让代码变得更易懂简单,代码如下:

上述代码中通过async和await将Promise链式操作改写成同步写法,让代码在可读性及可维护性上变得更加简单。
1.7.4 Promise里的其他方法
在Promise类上提供静态方法创建Promise对象,可以通过Promise.resolve来创建一个resolved状态的Promise对象,也可以通过Promise.reject来创建一个rejected状态的Promise对象。代码如下:

同样也可以通过Promise.all来执行多个Promise对象,代码如下:

使用Promise.all()函数时需要注意,接收的参数是一个数组,当所有Promise对象都执行成功之后才会拿到执行结果的数组。Promise.race()方法则不同,会返还最先执行的结果,无论成功还是失败。

上述代码中,三个异步Promise对象中p2会执行得最快,所以then回调里得到的就是p2的执行结果。