JavaScript - Promise
ES6 引入的一种异步编程方案,它会在未来的某个时间点交付异步操作的结果,这个结果既可以是成功的,也可以是失败的。
1. Promise 对象
一个Promise对象代表一次异步操作,且在生成后被立即执行
Promise对象有三种状态:
pending : 初始状态,既不是成功,也不是失败状态
fulfilled : 意味着操作成功完成
rejected : 意味着操作失败
通过new Promise()创建一个Promise对象
resolve将把 Promise 对象从pending变为fulfilled状态
reject将把 Promise 对象从pending变为rejected状态
2. Promise 实例方法
then() 接受两个方法作为参数,分别用来指定成功状态与失败状态的回调,两个方法接受的参数由 Promise 对象中的resolve与reject提供。
catch() 处理异常的方法,参数为一个方法,与then的第二个回调方法类似,可以用于处理失败状态。不同点在于catch也可以获取到回调方法中的代码异常,所以实际使用时,经常使用以下写法。
finally() 在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。该方法没有参数。
3. Promise 静态方法
Promise.resolve() 创建一个立即fulfilled的Promise对象
Promise.reject() 创建一个立即rejected的Promise对象
Promise.all() 参数为一个Promise数组,用于统一处理一组Promise对象的异步调用结果
当数组中所有的对象都为resolve时,新对象状态变为fulfilled
当数组中有一个对象reject时,新对象变为rejected
Promise.race() 参数也为一个Promise对象数组,数组中有一个对象resolve时,新对象状态变为fulfilled;当有一个对象reject时,新对象状态变为rejected。race方法常用于超时处理。
4. 链式调用
在 2 中,then(),catch(),finally() 方法以链状的形式被先后调用,其能够执行的原因在于这三个方法本身都将 return 一个Promise实例,返回实例能够继续调用Promise的实例方法。
5. Promise、setTimeout、async/await 执行顺序问题
a. js 事件循环机制(Event Loop)
在了解这三者的执行问题前,先来回顾一下 js 事件循环机制。众所周知,js 自古以来就是一门单线程,非阻塞的语言。单线程意味着只有一个主线程来执行所有的任务,也就是说同一时间只能执行一个任务。
Event Loop:分为主线程、宏任务、微任务三部分
主线程:可以直接执行的 js 代码
宏任务:setTimeout,setInterval
微任务:Promise.then(),new Promise主体部分的内容属于宏任务,可以直接执行。另外带 async 关键字的方法本身会返回一个 Promise 对象,可以理解为有 await 关键字的方法及其之前的部分为 Promise 对象的主体内容。带关键字await的方法之后的内容就是 Promise.then() 部分。
执行机制关键点:
优先执行主线程
遇到宏任务时,将其放入宏队列,遇到微任务时,将其放入微队列
主线程执行完毕后,执行微队列中的所有微任务直至清空
执行一个宏任务,宏任务完成后,查看主线程或微队列是否为空,若为空则继续执行
对于 setTimeout 而言,会在时间到了之后才会进入宏队列,而进入宏队列不意味着执行,也因此 setTimeout 内的代码的时机执行间隔是随机且略大于设定时间的。
了解了机制之后,举一个例子
挨个进行分析
首先主线程从 9 开始执行,毫无疑问先输出 script start;
10~22,23~25。两个 setTimeout 方法,宏任务,由于主线程还没执行结束,所以先放入宏队列,分别记为 M1 和 M2;
26 为带 async 关键字的方法,先进入,2~3 为主线程代码,所以输出 async1 start,然后执行 async2();
async2()这个方法的 async 关键字在这个例子中其实是多余的,该方法可以视作一个普通的方法,所以为主线程内容,输出 async2;
4~5 为 await 之后,相当于 Promise.then 部分,主线程还没结束,扔进微队列,记为 m1;
主线程现在到了 27,28 为 Promise 对象主体,主线程内容,输出 promise1;
30~32,Promise.then 部分,扔进微队列,记为 m2;
33,主线程,输出 script end,到此主线程暂时执行完毕;
主线程完毕,开始看微队列,先进先出原则,首先执行 m1,输出 async1 end;
微队列没有结束,继续执行微队列中的 m2,故输出 promise2,到此微队列暂时也告一段落;
接下来开始执行宏队列中的 M1,11~12 相当于主线程代码块,直接运行,输出 promise3;
13 为 resolve 方法,执行后将 15~20 扔进微队列,记为 m3;
21 为当前宏任务主代码块内容,直接输出 setTimeout1。至此,M1 宏任务结束;
此时,微队列增加 m3,故要先去执行微队列,发现 16~18 又是一个宏任务,记为 M3;
直接执行 19,输出 promise4;
微队列再次清空,执行宏队列中的 M2,输出 setTimeout2;
一个宏任务结束,查看主线程与微队列都为空,继续执行宏队列的 M3,输出 setTimeout3,至此,代码全部执行完成。
执行结果
结语: 代码执行顺序问题引发的 bug 在平时工作中经常出现,了解 js 代码以及各个异步编程方法的执行机制,可以有效规避这类问题。
最后更新于