实践了一个符合 Promise/A+ 规范的 repromise
Promise 札记 研究 Promise 的动机大体有以下几点:
基于这些目的,实践了一个符合 Promise/A+ 规范的 repromise
本札记系列总共三篇文章,作为之前的文章 Node.js 异步异闻录 的拆分和矫正。
Promise/A+ 核心
在实现一个符合 Promise/A+ 规范的 promise 之前,先了解下 Promise/A+ 核心,想更全面地了解可以阅读 Promise/A+规范
Promise 操作只会处在 3 种状态的一种:未完成态(pending)、完成态(resolved) 和失败态(rejected); Promise 的状态只会出现从未完成态向完成态或失败态转化; Promise 的状态一旦转化,将不能被更改; repromise api 食用手册 Promise.resolve() Promise.resolve() 括号内有 4 种情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Promise .resolve(Promise .resolve(1 ))var thenable = { then: function (resolve, reject ) { resolve(1 ) } } Promise .resolve(thenable)Promise .resolve(1 )Promise .resolve()
Promise.reject() 相较于 Promise.resolve(),Promise.reject() 原封不动地返回参数值
Promise.all(arr) 对于 Promise.all(arr) 来说,在参数数组中所有元素都变为决定态后,然后才返回新的 promise。
1 2 3 4 5 6 7 8 9 10 11 const p1 = request(`http://some.url.1` )const p2 = request(`http://some.url.2` )Promise .all([p1, p2]) .then((datas ) => { return request(`http://some.url.3?a=${datas[0 ]} &b=${datas[1 ]} ` ) }) .then((data ) => { console .log(msg) })
Promise.race(arr) 对于 Promise.race(arr) 来说,只要参数数组有一个元素变为决定态,便返回新的 promise。
1 2 3 4 5 6 7 8 9 10 11 const p1 = request(`http://some.url.1` )const p2 = request(`http://some.url.2` )Promise .race([p1, p2]) .then((data ) => { return request(`http://some.url.3?value=${data} ` ) }) .then((data ) => { console .log(data) })
Promise.wrap(fn) —— 回调函数转 Promise 通过下面这个案例,提供回调函数 Promise 化的思路。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function foo (a, b, cb ) { ajax( `http://some.url?a=${a} &b=${b} ` , cb ) } foo(1 , 2 , function (err, data ) { if (err) { console .log(err) } else { console .log(data) } })
如上是一个传统回调函数使用案例,只要使用 Promise.wrap() 包裹 foo 函数就对其完成了 promise 化,使用如下:
1 2 3 4 5 6 7 8 9 const promiseFoo = Promise .wrap(foo)promiseFoo(1 , 2 ) .then((data ) => { console .log(data) }) .catch((err ) => { console .log(err) })
Promise.wrap 的实现逻辑也顺带列出来了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Promise .wrap = function (fn ) { return funtion() { const args = [].slice.call(arguments ) return new Promise ((resolve, reject ) => { fn.apply(null , args.concat((err, data ) => { if (err) { reject(err) } else { resolve(data) } })) }) } }
then/catch/done 这几个 api 比较简单,合起来一起带过
1 2 3 4 Promise .resolve(1 ) .then((data ) => {console .log(data)}, (err) => {console .log(err)}) .catch((err ) => {console .log(err)}) .done()
实践过程总结 坑点 1:事件循环 事件循环:同步队列执行完后,在指定时间后再执行异步队列的内容。
之所以要单列事件循环,因为代码的执行顺序与其息息相关,此处用 setTimeout 来模拟事件循环;
下面代码片段中,① 处执行完并不会马上执行 setTimeout() 中的代码(③),而是此时有多少次 then 的调用,就会重新进入 ② 处多少次后,再进入 ③
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 excuteAsyncCallback(callback, value) { const that = this setTimeout(function ( ) { const res = callback(value) that.excuteCallback('fulfilled' , res) }, 4 ) } then(onResolved, onRejected) { const promise = new this .constructor() if (this .state !== 'PENDING' ) { const callback = this .state === 'fulfilled' ? onResolved : onRejected this .excuteAsyncCallback.call(promise, callback, this .data) } else { this .callbackArr.push(new CallbackItem(promise, onResolved, onRejected)) } return promise }
坑点 2:this 的指向问题 this.callbackArr.push() 中的 this 指向的是 ‘上一个’ promise,所以类 CallbackItem 中,this.promise 存储的是’下一个’ promise(then 对象)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Promise { ... then(onResolved, onRejected) { const promise = new this .constructor() if (this .state !== 'PENDING' ) { const callback = this .state === 'fulfilled' ? onResolved : onRejected this .excuteAsyncCallback.call(promise, callback, this .data) } else { this .callbackArr.push(new CallbackItem(promise, onResolved, onRejected)) } return promise } ... } class CallbackItem { constructor (promise, onResolve, onReject) { this .promise = promise this .onResolve = typeof (onResolve) === 'function' ? onResolve : (resolve ) => {} this .onReject = typeof (onRejected) === 'function' ? onRejected : (rejected ) => {} } ... }
more 实践的更多过程可以参考测试用例 。有好的意见欢迎交流。