更好的阅度体验html
做为一个前端开发,使用了Promise一年多了,一直以来都停留在API的调用阶段,没有很好的去深刻。恰好最近阅读了V8团队的一篇如何实现更快的async await,借着这个机会整理了Promise的相关理解。文中若有错误,请轻喷~前端
Promise是社区中对于异步的一种解决方案,相对于回调函数和事件机制更直观和容易理解。ES6 将其写进了语言标准,统一了用法,提供了原生的Promise对象。 git
这里只对API的一些特色作记录,若是须要详细教程,推荐阮老师的Promise对象一文 es6
new Promise
--建立一个promise实例 github
Promise.prototype.then(resolve, reject)
--then方法返回一个新的Promise实例 segmentfault
Promise.prototype.catch(error)
--.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
--错误会一直传递,直到被catch,若是没有catch,则没有任何反应
--catch返回一个新的Promise实例 数组
Promise.prototype.finally()
--指定无论 Promise 对象最后状态如何,都会执行的操做。
--实现以下:promise
Promise.prototype.finally = function (callback) { let P = this.constructor; return this.then( value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) ); };
Promise.all([promise Array])
--将多个 Promise 实例,包装成一个新的 Promise 实例
--全部子promise执行完成后,才执行all的resolve,参数为全部子promise返回的数组
--某个子promise出错时,执行all的reject,参数为第一个被reject的实例的返回值
--某个子promise本身catch时,不会传递reject给all,由于catch从新返回一个promise实例 异步
Promise.race([promise Array])
--将多个 Promise 实例,包装成一个新的 Promise 实例。
--子promise有一个实例率先改变状态,race的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给race的回调函数。 async
Promise.resolve()
--将现有对象转为 Promise 对象
--参数是promise实例, 原封不动的返回
--参数是一个thenable对象 将这个对象转为 Promise 对象,状态为resolved
--参数是一个原始值 返回一个新的 Promise 对象,状态为resolved
--不带有任何参数 返回一个resolved状态的 Promise 对象。
--等价于以下代码
Promise.resolve('foo') // 等价于 new Promise(resolve => resolve('foo'))
Promise.reject()
--返回一个新的 Promise 实例,该实例的状态为rejected
--Promise.reject()方法的参数,会原封不动地做为reject的理由,变成后续方法的参数。
不少文章都是把resolve当成fulfilled,本文也是,但本文还有另一个resolved,指的是该Promise已经被处理,注意二者的区别
1. 对象具备三个状态,分别是pending(进行中)、fulfilled(resolve)(已成功)、reject(已失败),而且对象的状态不受外界改变,只能从pending到fulfilled或者pending到reject。 2. 一旦状态被改变,就不会再变,任什么时候候都能获得这个结果,与事件回调不一样,事件回调在事件过去后没法再调用函数。 3. 一个promise一旦resolved,再次resolve/reject将失效。即只能resolved一次。 4. 值穿透,传给then或者catch的参数为非函数时,会发生穿透(下面有示例代码) 5. 没法取消,Promise一旦运行,没法取消。 6. 若是不设置回调函数,Promise内部抛出的错误,不会反应到外部 7. 处于pending时,没法感知promise的状态(刚刚开始仍是即将完成)。
new Promise(resolve=>resolve(8)) .then() .then() .then(function foo(value) { console.log(value) // 8 })
状态追随的概念和下面的v8处理asyac await相关联
状态跟随就是指将一个promise(代指A)当成另一个promise(代指B)的resolve参数,即B的状态会追随A。
以下代码所示:
const promiseA = new Promise((resolve) => { setTimeout(() => { resolve('ccc') }, 3000) }) const promiseB = new Promise(res => { res(promiseA) }) promiseB.then((arg) => { console.log(arg) // print 'ccc' after 3000ms })
按理说,promiseB应该是已经处于resolve的状态, 可是依然要3000ms后才打印出咱们想要的值, 这不免让人困惑
在ES的标准文档中有这么一句话能够帮助咱们理解:
A resolved promise may be pending, fulfilled or rejected.
就是说一个已经处理的promise,他的状态有多是pending, fulfilled 或者 rejected。 这与咱们前面学的不同啊, resolved了的promise不该该是处于结果状态吗?这确实有点反直觉,结合上面的例子看,当处于状态跟随时,即便promiseB当即被resolved了,可是由于他追随了promiseA的状态,而A的状态则是pending,因此才说处于resolved的promiseB的状态是pending。
再看另一个例子:
const promiseA = new Promise((resolve) => { setTimeout(() => { resolve('ccc') }, 3000) }) const promiseB = new Promise(res => { setTimeout(() => { res(promiseA) }, 5000) }) promiseB.then((arg) => { console.log(arg) // print 'ccc' after 5000ms })
其实理解了上面的话,这一段的代码也比较容易理解,只是由于本身以前进了牛角尖,因此特地记录下来:
在进入正题以前,咱们能够先看下面这段代码:
const p = Promise.resolve(); (async () => { await p; console.log("after:await"); })(); p.then(() => { console.log("tick:a"); }).then(() => { console.log("tick:b"); });
V8团队的博客中, 说到这段代码的运行结果有两种:
Node8(错误的):
after: await tick a tick b
Node10(正确的):
tick a tick b after await
ok, 问题来了, 为啥是这个样子?
先从V8对于await的处理提及, 这里引用一张官方博客的图来讲明Node8 await的伪代码:
对于上面的例子代码翻译过来就(该代码引用自V8是怎么实现更快的async await)是:
const p = Promise.resolve(); (() => { const implicit_promise = new Promise(resolve => { const promise = new Promise(res => res(p)); promise.then(() => { console.log("after:await"); resolve(); }); }); return implicit_promise; })(); p.then(() => { console.log("tick:a"); }).then(() => { console.log("tick:b"); });
很明显,内部那一句 new Promise(res => res(p)); 代码就是一个状态跟随,即promise追随p的状态,那这跟上面的结果又有什么关系?
在继续深刻以前, 咱们还须要了解一些概念:
JavaScript 中有 task 和 microtask 的概念。 Task 处理 I/O 和计时器等事件,一次执行一个。 Microtask 为 async/await 和 promise 实现延迟执行,并在每一个任务结束时执行。 老是等到 microtasks 队列被清空,事件循环执行才会返回。
如官方提供的一张图:
EnquequeJob: 存放两种类型的任务, 即PromiseResolveThenableJob和PromiseReactionJob, 而且都是属于microtask类型的任务
PromiseReactionJob: 能够通俗的理解为promise中的回调函数
PromiseResolveThenableJob(promiseToResolve, thenable, then): 建立和 promiseToResolve 关联的 resolve function 和 reject function。以 then 为调用函数,thenable 为this,resolve function和reject function 为参数调用返回。(下面利用代码讲解)
再以以前的代码为例子
const promiseA = new Promise((resolve) => { resolve('ccc') }) const promiseB = new Promise(res => { res(promiseA) })
当promiseB被resolved的时候, 也就是将一个promise(代指A)当成另一个promise(代指B)的resolve参数,会向EnquequeJob插入一个PromiseResolveThenableJob任务,PromiseResolveThenableJob大概作了以下的事情:
() => { promiseA.then( resolvePromiseB, rejectPromiseB ); }
而且当resolvePromiseB执行后, promiseB的状态才变成resolve,也就是B追随A的状态
1. p处于resolve状态,promise调用then被resolved,同时向microtask插入任务PromiseResolveThenableJob 2. p.then被调用, 向microtask插入任务tickA 3. 执行PromiseResolveThenableJob, 向microtask插入任务resolvePromise(如上面的promiseA.then(...)) 4. 执行tickA(即输出tick: a),返回一个promise, 向microtask插入任务tickB 5. 由于microtask的任务还没执行完, 继续 6. 执行resolvePromise, 此时promise终于变成resolve, 向microtask插入任务'after await' 7. 执行tickB(即输出tick: b) 8. 执行'after await'(即输出'after await')
老规矩, 先补一张伪代码图:
翻译过来就是酱紫:
const p = Promise.resolve(); (() => { const implicit_promise = new Promise(resolve => { const promise = Promise.resolve(p) promise.then(() => { console.log("after:await"); resolve(); }); }); return implicit_promise; })(); p.then(() => { console.log("tick:a"); }).then(() => { console.log("tick:b"); });
由于p是一个promise, 而后Promise.resolve会直接将P返回,也就是
p === promise // true
由于直接返回了p,因此省去了中间两个microtask任务,而且输出的顺序也变得正常,也就是V8所说的更快的async await
先实现一个基础的函数
function Promise(cb) { const that = this that.value = undefined // Promise的值 that.status = 'pending' // Promise的状态 that.resolveArray = [] // resolve函数集合 that.rejectArray = [] // reject函数集合 function resolve(value) { if (value instanceof Promise) { return value.then(resolve, reject) } setTimeout(function() { if (that.status === 'pending') { // 处于pending状态 循环调用 that.value = value that.status = 'resolve' for(let i = 0; i < that.resolveArray.length; i++) { that.resolveArray[i](value) } } }) } function reject(reason) { if (reason instanceof Promise) { return reason.then(resolve, reject) } setTimeout(function() { if (that.status === 'pending') { // 处于pending状态 循环调用 that.value = reason that.status = 'reject' for(let i = 0; i < that.rejectArray.length; i++) { that.rejectArray[i](reason) } } }) } try { cb(resolve, reject) } catch (e) { reject(e) } } Promise.prototype.then = function(onResolve, onReject) { var that = this var promise2 // 返回的Promise onResolve = typeof onResolve === 'function' ? onResolve : function(v) { return v } //若是不是函数 则处理穿透值 onReject = typeof onReject === 'function' ? onReject : function(v) { return v } //若是不是函数 则处理穿透值 if (that.status === 'resolve') { return promise2 = new Promise(function(resolve, reject) { setTimeout(function() { try { const x = onResolve(that.value) if (x instanceof Promise) { // 若是onResolved的返回值是一个Promise对象,直接取它的结果作为promise2的结果 x.then(resolve, reject) } else { resolve(x) } } catch (e) { reject(e) } }) }) } if (that.status === 'reject') { return promise2 = new Promise(function(resolve, reject) { setTimeout(function() { try { const x = onResolve(that.value) if (x instanceof Promise) { // 若是onResolved的返回值是一个Promise对象,直接取它的结果作为promise2的结果 x.then(resolve, reject) } else { reject(x) } } catch (e) { reject(e) } }) }) } if (that.status === 'pending') { return promise2 = new Promise(function(resolve, reject) { that.resolveArray.push(function(value) { try { var x = onResolve(value) if (x instanceof Promise) { x.then(resolve, reject) } } catch (e) { reject(e) } }) that.rejectArray.push(function(reason) { try { var x = onReject(reason) if (x instanceof Promise) { x.then(resolve, reject) } } catch (e) { reject(e) } }) }) } } Promise.prototype.catch = function(onReject) { return this.then(null, onReject) }
v8是怎么实现更快的 await ?深刻理解 await 的运行机制
V8中更快的异步函数和promise
剖析Promise内部结构,一步一步实现一个完整的、能经过全部Test case的Promise类
PromiseA+
ES6入门
深刻Promise