手写一个Promise
并通过Promise A+
规范测试。
Promise
构造函数在规范中,有一段
the core Promises/A+ specification does not deal with how to create, fulfill, or reject promises, choosing instead to focus on providing an interoperable then method. Future work in companion specifications may touch on these subjects.
可以看出,规范并没有指出如何创建一个Promise
,而是规范then
方法的行为。
而在 ES6 中创建一个Promise
是通过new Promise(executor)
来实现的。具体可以看 MDN 上对 Promise 的解释。
Promise - JavaScript | MDN
所以我们也从这一个角度入手,创建一个我们自己的Promise
构造函数,命名为MyPromise
。
1 2 3 function MyPromise ( ) { }
ES6
的Promise
构造函数需要传入一个executor
函数,该函数在Promise
内部会立即执行。
所以我们的构造函数也传入一个executor
函数。
1 2 3 function MyPromise (executor ) { }
规范中提到:
A promise must be in one of three states: pending, fulfilled, or rejected.
也就是一个Promise
必须为三个状态中的其中一个。
OK,那我们定义三个状态的常量来表示:
1 2 3 4 5 6 const PENDING = "pending" ;const RESOLVED = "resolved" ;const REJECTED = "rejected" ;
接下来我们应该初始化一个Promise
对象上的一些属性。
1 2 3 4 5 6 7 8 9 10 11 12 function MyPromise (executor ) { const self = this ; self.$status = PENDING ; self.$data = undefined ; self.$onResolvedCallbacks = []; self.$onRejectedCallbacks = []; }
对于传入的executor
函数,有两个参数:
resolve
完成这个Promise
的函数;
reject
拒绝这个Promise
的函数;
所以我们在构造函数的内部需要实现这两个函数,并且在执行executor
函数时,传进这两个函数。
在ES6
的Promise
实现中,一般我们会这么使用Promise
1 2 3 4 5 6 7 8 9 var promise = new Pormise (function (resolve, reject ) { resolve (1 ); }); promise.then ((res ) => { console .log (res); });
所以我们的实现也模仿它。
记住,Promise
只能是三个状态中的一个,并且只能从:
pending
-> resolved
pending
-> rejected
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 function MyPromise (executor ) { const self = this ; self.$status = PENDING ; self.$data = undefined ; self.$onResolvedCallbacks = []; self.$onRejectedCallbacks = []; function resolve (value ) { if (self.$status === PENDING ) { self.$status === RESOLVED ; self.$data = value; self.$onResolvedCallbacks .forEach ((fn ) => { fn (value); }); } } function reject (reason ) { if (self.$status === PENDING ) { self.$status === REJECTED ; self.$data = reason; self.$onRejectedCallbacks .forEach ((fn ) => { fn (reason); }); } } }
两个函数都被if
判定的语句包裹,防止一个已经resolve
的Promise
对象被reject
,或者已经被reject
的Promise
对象被resolve
。
其实这么写还是有问题的,不过后面再说。
现在我们的构造函数基本完成了大部分的工作,最后的一步就是执行executor
函数,并传入resolve
和reject
函数了。
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 function MyPromise (executor ) { const self = this ; self.$status = PENDING ; self.$data = undefined ; self.$onResolvedCallbacks = []; self.$onRejectedCallbacks = []; function resolve (value ) { if (self.$status === PENDING ) { self.$status === RESOLVED ; self.$data = value; self.$onResolvedCallbacks .forEach ((fn ) => { fn (value); }); } } function reject (reason ) { if (self.$status === PENDING ) { self.$status === REJECTED ; self.$data = reason; self.$onRejectedCallbacks .forEach ((fn ) => { fn (reason); }); } } try { executor (resolve, reject); } catch (e) { reject (e); } }
为什么执行executor
函数要放在try-catch
中呢?
因为如果executor
执行过程出错我们希望以出错的原因来拒绝这个Promise
。
ES6
的实现也是如此。
1 2 3 4 5 6 7 var promise = new Promise (function (resolve, reject ) { throw new Error (); }); promise.then (null , (err ) => { console .log ("出错啦~" ); });
Promise
对象的then
函数每一个Promise
对象都可以调用它的then
方法来注册回调函数,所以then
方法要写在构造函数的原型对象上:
1 2 3 MyPromise .prototype .then = function ( ) { };
在规范2.2.1
中,提到then
方法需要有两个参数:
A promise’s then method accepts two arguments:promise.then(onFulfilled, onRejected)
onFulfilled
:Promise
被解决时的回调。(下文用onResolved
代替,都是一个意思的。)
onRejected
:promise
被拒绝时的回调。
所以我们的then
方法也传进这两个参数:
1 2 3 MyPromise .prototype .then = function (onResolved, onRejected ) { };
紧接着,规范说明这两个参数都是可选的,当不是函数的时候要进行忽略。所以在函数的内部要进行判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 MyPromise .prototype .then = function (onResolved, onRejected ) { onResolved = typeof onResolved === "function" ? onResolved : function (value ) { return value; }; onRejected = typeof onRejected === "function" ? onRejected : function (err ) { throw err; }; };
规范2.2.7
说明then
方法必须返回一个新的Promise
对象。
then must return a promise.promise2 = promise1.then(onFulfilled, onRejected);
因为Promise
有三个状态,所以我们需要为每一个状态编写返回函数:
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 MyPromise .prototype .then = function (onResolved, onRejected ) { onResolved = typeof onResolved === "function" ? onResolved : function (value ) { return value; }; onRejected = typeof onRejected === "function" ? onRejected : function (err ) { throw err; }; if (this .$status === RESOLVED ) { return new MyPromise (function (resolve, reject ) { }); } if (this .$status === REJECTED ) { return new MyPromise (function (resolve, reject ) { }); } if (this .$status === PENDING ) { return new MyPromise (function (resolve, reject ) { }); } };
规范2.2.7.1 - 2.2.7.4
说明了then
方法放回的Promise
的状态如何根据传入函数(onFulfilled, onRejected
)的返回值决定。
If either onFulfilled
or onRejected
returns a value x
, run the Promise Resolution Procedure [[Resolve]](promise2, x)
. If either onFulfilled
or onRejected
throws an exception e
, promise2
must be rejected with e
as the reason. If onFulfilled
is not a function and promise1
is fulfilled, promise2
must be fulfilled with the same value as promise1
. If onRejected
is not a function and promise1
is rejected, promise2
must be rejected with the same reason as promise1
.
这里的重点时如何根据返回值x
来决定返回的Promise
的状态,这里需要引出第一点的[[Resolve]](promise2, x)
这个函数。
这个函数在2.3
有详细的介绍,这里我们先不去管函数的内部如何实现,我们只要明白,这个函数根据x
来决定promise2
的状态。
1 2 3 4 5 6 7 8 9 function PromiseResolution (promise, x, resolve, reject ) { }
现在返回Promise
的内部就可以写了,对于三个状态:
resolved
状态,直接调用then
注册的onResolved
函数即可。
rejected
状态,直接调用then
注册的onRejected
函数即可。
pending
状态,需要在状态变更的时候,调用注册的函数,也就是往$onResolvedCallbacks
和$onRejectedCallbacks
中储存一个函数。
并且规范2.2.4
以及3.1
指出onResolved
和onRejected
必须异步的执行,所以我们要包裹函数内的操作:
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 MyPromise .prototype .then = function (onResolved, onRejected ) { const self = this ; let promise; onResolved = typeof onResolved === "function" ? onResolved : function (value ) { return value; }; onRejected = typeof onRejected === "function" ? onRejected : function (err ) { throw err; }; if (this .$status === RESOLVED ) { return (promise = new MyPromise (function (resolve, reject ) { setTimeout (() => { try { const res = onResolved (self.$data ); PromiseResolution (promise, res, resolve, reject); } catch (e) { reject (e); } }, 0 ); })); } if (this .$status === REJECTED ) { return (promise = new MyPromise (function (resolve, reject ) { setTimeout (() => { try { const res = onRejected (self.$data ); PromiseResolution (promise, res, resolve, reject); } catch (e) { reject (e); } }, 0 ); })); } if (this .$status === PENDING ) { return (promise = new MyPromise (function (resolve, reject ) { self.$onResolvedCallbacks .push (function (value ) { try { const res = onResolved (value); PromiseResolution (promise, res, resolve, reject); } catch (e) { reject (e); } }); self.$onRejectedCallbacks .push (function (err ) { try { const res = onRejected (err); PromiseResolution (promise, res, resolve, reject); } catch (e) { reject (e); } }); })); } };
至此,我们的主体代码部分基本完成,最后就差PromiseResolution
这个函数的实现了。 这个函数的实现在规范中其实说的很详细,只要按着要求来基本就没啥大问题了。
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 function PromiseResolution (promise, x, resolve, reject ) { if (promise === x) { reject (new TypeError ()); return ; } if (x instanceof Promise ) { x.then (resolve, reject); } else if (x !== null && (typeof x === "object" || typeof x === "function" )) { let then; try { then = x.then ; } catch (e) { reject (e); return ; } if (typeof then === "function" ) { let whoCall = false ; try { then.call ( x, (y ) => { if (!whoCall) { whoCall = true ; PromiseResolution (promise, y, resolve, reject); } }, (err ) => { if (!whoCall) { whoCall = true ; reject (err); } } ); } catch (e) { if (!whoCall) { whoCall = true ; reject (e); } } } else { resolve (x); } } else { resolve (x); } }
至此,我们的Promise
实现基本完成。
但是如果以下面的代码来测试我们的Promise
的话,会发现无法打印:
1 2 3 4 5 6 7 const promise = new MyPromise (function (resolve, reject ) { resolve (1 ); }); promise.then ((res ) => { console .log (res); });
这是为什么呢,重点就是我们在构造函数内部的resolve
和reject
函数,它们是同步,也就是:
1 2 3 4 5 6 7 8 const promise = new MyPromise (function (resolve, reject ) { resolve (1 ); }); promise.then ((res ) => { console .log (res); });
解决的办法就是以异步的形式来解决Promise
,以一个新的任务来解决Promise
,也就是包裹在setTimeout
中即可。
测试 现在来使用Promise/A+
官方给定的测试数据来测试这个实现。
github:测试脚本
我们根据README.md
,其实很简单,就是配置一个依赖,然后运行特定的命令即可。
首先先创建一个项目,使用npm init
来初始化,package.json
使用默认的即可。
接着导入这个库。
1 2 3 4 5 6 7 { "devDependencies" : { "promises-aplus-tests" : "*" } }
1 2 3 4 5 6 7 8 9 10 { "devDependencies" : { "promises-aplus-tests" : "*" } , "scripts" : { "test" : "promises-aplus-tests test/index.js" } }
最后,需要以一个es6 module
的形式来导出一个对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const deferred = function ( ) { let defer = {}; defer.promise = new MyPromise ((resolve, reject ) => { defer.resolve = resolve; defer.reject = reject; }); return defer; }; module .exports = { deferred, };
接着,就可以运行npm test
的来进行愉快地测试了。
附上完整的代码:
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 158 159 160 161 162 const PENDING = "pending" ;const RESOLVED = "resolved" ;const REJECTED = "rejected" ;function MyPromise (executor ) { const self = this ; self.$status = PENDING ; self.$data = undefined ; self.$onResolvedCallbacks = []; self.$onRejectedCallbacks = []; function resolve (value ) { setTimeout (() => { if (self.$status === PENDING ) { self.$status = RESOLVED ; self.$data = value; self.$onResolvedCallbacks .forEach ((fn ) => { fn (value); }); } }, 0 ); } function reject (err ) { setTimeout (() => { if (self.$status === PENDING ) { self.$status = REJECTED ; self.$data = err; self.$onRejectedCallbacks .forEach ((fn ) => { fn (err); }); } }, 0 ); } try { executor (resolve, reject); } catch (e) { reject (e); } } MyPromise .prototype .then = function (onResolved, onRejected ) { const self = this ; let promise2; onResolved = typeof onResolved === "function" ? onResolved : function (value ) { return value; }; onRejected = typeof onRejected === "function" ? onRejected : function (err ) { throw err; }; if (self.$status === RESOLVED ) { return (promise2 = new MyPromise (function (resolve, reject ) { setTimeout (() => { try { const res = onResolved (self.$data ); PromiseResolve (promise2, res, resolve, reject); } catch (e) { reject (e); } }, 0 ); })); } if (self.$status === REJECTED ) { return (promise2 = new MyPromise (function (resolve, reject ) { setTimeout (() => { try { const res = onRejected (self.$data ); PromiseResolve (promise2, res, resolve, reject); } catch (e) { reject (e); } }, 0 ); })); } if (self.$status === PENDING ) { return (promise2 = new MyPromise (function (resolve, reject ) { self.$onResolvedCallbacks .push (function (value ) { try { const res = onResolved (value); PromiseResolve (promise2, res, resolve, reject); } catch (e) { reject (e); } }); self.$onRejectedCallbacks .push (function (err ) { try { const res = onRejected (err); PromiseResolve (promise2, res, resolve, reject); } catch (e) { reject (e); } }); })); } }; function PromiseResolve (promise, x, resolve, reject ) { if (promise === x) { reject (new TypeError ()); return ; } if (x instanceof Promise ) { x.then (resolve, reject); } else if (x !== null && (typeof x === "object" || typeof x === "function" )) { let then; try { then = x.then ; } catch (e) { reject (e); return ; } if (typeof then === "function" ) { let whoCall = false ; try { then.call ( x, (y ) => { if (!whoCall) { whoCall = true ; PromiseResolve (promise, y, resolve, reject); } }, (err ) => { if (!whoCall) { whoCall = true ; reject (err); } } ); } catch (e) { if (!whoCall) { whoCall = true ; reject (e); } } } else { resolve (x); } } else { resolve (x); } } module .exports = MyPromise ;
附上自己地测试截图