手写一个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 ;
附上自己地测试截图