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 实现细节