Promise/A+ 规范与 Promise 实现代码
Promise promise 表示一个异步操作的最终结果。和一个 promise 进行交互的主要方式是通过它的 then 方法,该方法注册回调要么接收一个 promise 的最终值,要么接收 promise 为什么不能被满足的原因。
规范 Promise A+ 规范
术语
promise 是一个带有 then 方法的对象或函数,其行为符合此规范。
thenable 是一个定义了 then 方法的对象或函数。
value 是任何合法的 JavaScript 值(包括undefined、thenable或promise)。
exception 是使用 throw 语句抛出的值。
reason 是一个值,它指示为什么一个承诺被拒绝。
要求
promise状态
pending:等待状态
当处于等待状态时可以转化成 fulfilled / rejected
fulfilled:完成状态
一定不能转换成其他状态
必须有一个不能转换的值(value)
rejected:拒绝状态
一定不能转换成其他状态
必须又一个不能转换的原因(reason)
then 方法:一个 promise 必须提供一个 then 方法来访问它的当前或最终值(value)或原因(reason),then 方法接收两个参数 promise.then(onFulfilled, onRejected)
onFulfilled 和 onRejected 都是可选的参数
如果 onFulfilled 不是个函数,必须被忽略
如果 onRejected 不是个函数,必须被忽略
如果 onFulfilled 是一个函数
必须在 promise 完成(resolve)后被调用,promise 的值(value)作为它第一个参数
一定不能在 promise 完成(resolve)前被调用
一定不能被调用多次
如果 onRejected 是一个函数
必须在 promise 拒绝(reject)后被调用,promise 的值(reason)作为它第一个参数
一定不能在 promise 拒绝(reject)前被调用
一定不能被调用多次
在执行上下文堆栈只包含平台代码[3.1]之前,不能调用 onfulfilled 或 onRejected
onFulfilled 和 onRejected 必须作为函数调用(没有 this 值)[3.2]
同一个 promise 上的 then 可能被多次调用
如果 promise 被完成(resolve),所有相应的 onFulfilled 需要按照 then 的顺序被顺序执行
如果 promise 被拒绝(reject),所有相应的 onRejected 需要按照 then 的顺序被顺序执行
then 必须返回一个 promise 实例 [3.3] promise2 = promise1.then(onFulfilled, onRejected)
如果 onFulfilled 和 onRejected 返回一个值 x,运行 [[Resolve]](promise2, x)
如果 onFulfilled 和 onRejected 抛出一个异常 e ,promise2 必须要用 e 作为理由拒绝
如果 onFulfilled 不是一个函数并且 promise1 被完成(resolve),promise2 需要返回跟 promise1 同样的值 (value)
如果 onRejected 不是一个函数并且 promise1 被拒绝(reject),promise2 需要返回跟 promise1 同样的值 (reason)
promise 解决程序([[Resolve]])[[Resolve]] 是一个抽象的概念,它一个 promise 和 x 作为输入 [[Resolve]](promise, x),如果 x 是一个 thenable,它试图让承诺采用 x 的状态,假设 x 至少表现得有点像一个promise。否则,它将使用值x来实现 promise,这种 thenable 的处理允许 promise 的实现更具有通用型,只要它们暴露一个遵守 Promise/A+ 的 then 方法即可。它还允许遵守Promise/A+规范的实现可以与那些不太规范但是可用的实现进行共存,运行[[Resolve]](promise, x),执行以下步骤
如果 promise 和 x 指向同一个对象,则以 TypeError 作为原因拒绝 promise
如果 x 是一个 promise 则采取它的状态 [3.4]
如果 x 状态是 pending,则 promise 必须持续 pending,直到 x 被完成或拒绝。
如果 x 状态是 resolve,则用相同的值(value)解决 promise
如果 x 状态是 reject,则用相同的原因(reason)拒绝 promise
如果 x 是一个函数或者对象
使 then 的值为 x.then [3.5]
如果获取 x.then 的值抛出异常 e, 则将 e 作为原因拒绝 promise
如果 then 是一个函数,用 x 作为 this 调用它,第一个参数resolvePromise ,第二个参数 rejectPromise ,其中
如果当 resolvePromise 的值为 y 时,运行 [[Resolve]](promise, y)
如果 rejectPromise 用一个原因 r 调用,用 r 拒绝 promise
如果同时调用了 resolvePromise 和 rejectPromise ,或者多次调用相同的参数,那么第一次调用优先,后续的调用将被忽略。(针对 thenable )
如果调用 then 抛出一个异常 e
如果 resolvePromise 或 rejectPromise 已经被调用,忽略它。
否则用 e 作为原因拒绝 promise
如果 then 不是一个函数,用 x 完成(resolve) promise
如果 x 不是一个对象或函数,用 x 完成(resolve) promise
如果一个 promise 被 thenable 解析,并参与一个循环的 thenable 链,这样 [[Resolve]](promise, thenable) 的递归性质最终导致 [[Resolve]](promise, thenable) 再次被调用,按照上述算法将导致无限递归。我们鼓励(但不是必需)实现检测这种递归,并以 TypeError 作为原因拒绝承诺。[3.6]
注解
这里 平台代码 使引擎、环境以及promise的实现代码。在实践中,这需要确保 onFulfilled 和 onRejected 异步地执行,并且应该在 then 方法被调用的那一轮事件循环之后用新的执行栈执行。这可以用如 setTimeout 或 setImmediate 这样的“宏任务”机制实现,或者用如 MutationObserver 或 process.nextTick 这样的“微任务”机制实现。由于 promise 的实现被考虑为 平台代码 ,因此在自身处理程序被调用时可能已经包含一个任务调度队列
严格模式下,它们中的this将会是undefined;在非严格模式,this将会是全局对象
假如实现满足所有需求,可以允许 promise2 === promise1 。每一个实现都应该记录是否能够产生 promise2 === promise1 以及什么情况下会出现 promise2 === promise1
通常,只有 x 来自于当前实现,才知道它是一个真正的 promise 。这条规则允许那些特例实现采用符合已知要求的 Promise 的状态
这个程序首先存储 x.then 的引用,之后测试那个引用,然后再调用那个引用,这样避免了多次访问 x.then 属性。此类预防措施对于确保访问者属性的一致性非常重要,因为访问者属性的值可能在俩次检索之间发生变化
实现不应该在 thenable 链的深度上做任意限制,并且假设超过那个任意限制将会无限递归。只有真正的循环才应该引发一个 TypeError ;如果遇到一个无限循环的 thenable,永远执行递归是正确的行为
大纲图
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class MyPromise { status = PENDING value = null reason = null onFulfilledCallbacks = [] onRejectedCallbacks = [] constructor (executor ){ try { executor (this .resolve , this .reject ) } catch (error) { this .reject (error) } } resolve = value => { if (this .status === PENDING ){ this .status = FULFILLED this .value = value while (this .onFulfilledCallbacks .length ){ this .onFulfilledCallbacks .shift ()(this .value ) } } } reject = reason => { if (this .status === PENDING ){ this .status = REJECTED this .reason = reason while (this .onRejectedCallbacks .length ){ this .onRejectedCallbacks .shift ()(this .reason ) } } } then (onFulfilled, onRejected ){ const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason } const promise1 = new MyPromise ((resolve, reject ) => { const fulfilledMicrotask = ( ) => { queueMicrotask (() => { try { let x = realOnFulfilled (this .value ) resolvePromise (promise1, x, resolve, reject) }catch (error) { reject (error) } }) } const rejectMicrotask = ( ) => { queueMicrotask (() => { try { let x = realOnRejected (this .reason ) resolvePromise (promise1, x, resolve, reject) }catch (error) { reject (error) } }) } if (this .status == FULFILLED ){ fulfilledMicrotask () }else if (this .status == REJECTED ){ rejectMicrotask () }else { this .onFulfilledCallbacks .push (fulfilledMicrotask) this .onRejectedCallbacks .push (rejectMicrotask) } }) return promise1 } catch (onRejected) { this .then (null , onRejected) } } function resolvePromise (promise, x, resolve, reject ){ if (x === promise){ return reject (new TypeError ('The promise and the return value are the same' )); } if (typeof x === 'function' || typeof x === 'object' ){ if (x === null ) return resolve (x) let then try { then = x.then } catch (error) { return reject (error) } if (typeof then === 'function' ){ let called = false try { then.call (x, y => { if (called) return called = true resolvePromise (promise, y, resolve, reject) }, r => { if (called) return called = true reject (r) }) } catch (error) { if (called) return called = true reject (error) } }else { resolve (x) } }else { resolve (x) } } MyPromise .deferred = function ( ){ var result = {} result.promise = new MyPromise (function (resolve, reject ){ result.resolve = resolve result.reject = reject }) return result } module .exports = MyPromise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 var PENDING = 'pending' ;var REJECTED = 'rejected' ;var FULFILLED = 'fulfilled' ;function MyPromise (executor ){ this .status = PENDING this .value = null ; this .reason = null ; this .onFulfilled = [] this .onRejected = [] this .resolve = this .resolve .bind (this ) this .reject = this .reject .bind (this ) try { executor (this .resolve , this .reject ) } catch (error) { this .reject (error) } } MyPromise .prototype .resolve = function (value ){ if (this .status === PENDING ){ this .status = FULFILLED this .value = value while (this .onFulfilled .length ){ this .onFulfilled .shift ()(this .value ) } } } MyPromise .prototype .reject = function (reason ){ if (this .status === PENDING ){ this .status = REJECTED this .reason = reason while (this .onRejected .length ){ this .onRejected .shift ()(this .reason ) } } } MyPromise .prototype .then = function (onFulfilled, onRejected ){ var that = this onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value ){ return value } onRejected = typeof onRejected === 'function' ? onRejected : function (reason ){ throw reason } var promise1 = new MyPromise (function (resolve, reject ){ var fulfilled = function ( ){ queueMicrotask (function ( ){ try { var x = onFulfilled (that.value ) resolvePromise (promise1, x, resolve, reject) } catch (error) { reject (error) } }) } var rejected = function ( ){ queueMicrotask (function ( ){ try { var x = onRejected (that.reason ) resolvePromise (promise1, x, resolve, reject) } catch (error) { reject (error) } }) } if (that.status === FULFILLED ){ fulfilled () }else if (that.status === REJECTED ){ rejected () }else { that.onFulfilled .push (fulfilled) that.onRejected .push (rejected) } }) return promise1 } MyPromise .prototype .catch = function (onRejected ){ this .then (null , onRejected) }
执行流程
参考文章 从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节