-- What i can't create, i don't understantgit
实现Promise的目的是为了深刻的理解Promies,以在项目中游刃有余的使用它。完整的代码见gitHubgithub
Promise的标准有不少个版本,本文采用ES6原生Promise使用的Promise/A+标准。完整的Promise/A+标准见这里,总结以下:
promise
OK,搞清楚了promise标准以后,开始动手吧浏览器
产生一个对象有不少种方法,构造函数是看起来最面向对象的一种,并且原生Promise实现也是使用的构造函数,所以我也决定使用构造函数的方法。
首先,先写一个大概的框架出来:闭包
// 总所周知,Promise传入一个executor,有两个参数resolve, reject,用来改变promise的状态 function Promise(executor) { this.status = 'pending' this.value = void 0 // 为了方便把value和reason合并 const resolve = function() {} const reject = function() {} executor(resolve, reject) }
很明显,这个构造函数还有不少问题们一个一个来看框架
resolve和reject并无什么卵用。
首先,用过promise的都知道,resolve和reject是用来改变promise的状态的:异步
function Promise(executor) { this.status = 'pending' this.value = void 0 // 为了方便把value和reason合并 const resolve = value => { this.value = value this.status = 'resolved' } const reject = reason => { this.value = reason this.status = 'rejected' } executor(resolve, reject) }
而后,当resolve或者reject调用的时候,须要执行在then方法里传入的相应的函数(通知)。有没有以为这个有点相似于事件(发布-订阅模式)呢?函数
function Promise(executor) { this.status = 'pending' this.value = void 0 // 为了方便把value和reason合并 this.resolveListeners = [] this.rejectListeners = [] // 通知状态改变 const notify(target, val) => { target === 'resolved' ? this.resolveListeners.forEach(cb => cb(val)) : this.rejectListeners.forEach(cb => cb(val)) } const resolve = value => { this.value = value this.status = 'resolved' notify('resolved', value) } const reject = reason => { this.value = reason this.status = 'rejected' notify('rejected', reason) } executor(resolve, reject) }
status和value并无作到一旦肯定,没法更改。这里有两个问题,一是返回的对象暴露了status和value属性,而且能够随意赋值;二是若是在executor里屡次调用resolve或者reject,会使value更改屡次。
第一个问题,如何实现只读属性:
测试
function Promise(executor) { if (typeof executor !== 'function') { throw new Error('Promise executor must be fucntion') } let status = 'pending' // 闭包造成私有属性 let value = void 0 ...... // 使用status代替this.value const resolve = val => { value = val status = 'resolved' notify('resolved', val) } const reject = reason => { value = reason status = 'rejected' notify('rejected', reason) } // 经过getter和setter设置只读属性 Object.defineProperty(this, 'status', { get() { return status }, set() { console.warn('status is read-only') } }) Object.defineProperty(this, 'value', { get() { return value }, set() { console.warn('value is read-only') } })
第二个问题,避免屡次调用resolve、reject时改变value,并且标准里(2.2.2.3 it must not be called more than once)也有规定,then注册的回调只能执行一次。
ui
const resolve = val => { if (status !== 'pending') return // 避免屡次运行 value = val status = 'resolved' notify('resolved', val) }
then注册的回调须要异步执行。
说到异步执行,对原生Promise有了解的同窗都知道,then注册的回调在Micro-task中,而且调度策略是,Macro-task中执行一个任务,清空全部Micro-task的任务。简而言之,promise异步的优先级更高。
其实,标准只规定了promise回调须要异步执行,在一个“干净的”执行栈执行,并无规定必定说要用micro-task,而且在低版本浏览器中,并无micro-task队列。不过在各类promise的讨论中,因为原生Promise的实现,micro-task已经成成为了事实标准,并且promise回调在micro-task中也使得程序的行为更好预测。
在浏览器端,能够用MutationObserver实现Micro-task。本文利用setTimeout来简单实现异步。
const resolve = val => { if (val instanceof Promise) { return val.then(resolve, reject) } // 异步执行 setTimeout(() => { if (status !== 'pending') return status = 'resolved' value = val notify('resolved', val) }, 0) }
最后,加上错误处理,就获得了一个完整的Promise构造函数:
function Promise(executor) { if (typeof executor !== 'function') { throw new Error('Promise executor must be fucntion') } let status = 'pending' let value = void 0 const notify = (target, val) => { target === 'resolved' ? this.resolveListeners.forEach(cb => cb(val)) : this.rejectListeners.forEach(cb => cb(val)) } const resolve = val => { if (val instanceof Promise) { return val.then(resolve, reject) } setTimeout(() => { if (status !== 'pending') return status = 'resolved' value = val notify('resolved', val) }, 0) } const reject = reason => { setTimeout(() => { if (status !== 'pending') return status = 'rejected' value = reason notify('rejected', reason) }, 0) } this.resolveListeners = [] this.rejectListeners = [] Object.defineProperty(this, 'status', { get() { return status }, set() { console.warn('status is read-only') } }) Object.defineProperty(this, 'value', { get() { return value }, set() { console.warn('value is read-only') } }) try { executor(resolve, reject) } catch (e) { reject(e) } }
总的来讲,Promise构造函数其实只干了一件事:执行传入的executor,并构造了executor的两个参数。
首先须要肯定的是,then方法是写在构造函数里仍是写在原型里。
写在构造函数了里有一个比较大的好处:能够像处理status和value同样,经过闭包让resolveListeners和rejectListeners成为私有属性,避免经过this.rejectListeners来改变它。
写在构造函数里的缺点是,每个promise对象都会有一个不一样的then方法,这既浪费内存,又不合理。个人选择是写在原型里,为了保持和原生Promise有同样的结构和接口。
ok,仍是先写一个大概的框架:
Promise.prototype.then = function (resCb, rejCb) { this.resolveListeners.push(resCb) this.rejectListeners.push(rejCb) return new Promise() }
随后,一步一步的完善它:
then方法返回的promise须要根据resCb或rejCb的运行结果来肯定状态。
Promise.prototype.then = function (resCb, rejCb) { return new Promise((res, rej) => { this.resolveListeners.push((val) => { try { const x = resCb(val) res(x) // 以resCb的返回值为value来resolve } catch (e) { rej(e) // 若是出错,返回的promise以异常为reason来reject } }) this.rejectListeners.push((val) => { try { const x = rejCb(val) res(x) // 注意这里也是res而不是rej哦 } catch (e) { rej(e) // 若是出错,返回的promise以异常为reason来reject } }) }) }
ps:众所周知,promise能够链式调用,提及链式调用,个人第一个想法就是返回this就能够了,可是then方法不能够简单的返回this,而要返回一个新的promise对象。由于promise的状态一旦肯定就不能更改,而then方法返回的promise的状态须要根据then回调的运行结果来决定。
若是resCb/rejCb返回一个promiseA,then返回的promise须要跟随(adopt)promiseA,也就是说,须要保持和promiseA同样的status和value。
this.resolveListeners.push((val) => { try { const x = resCb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } }) this.rejectListeners.push((val) => { try { const x = resCb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } })
若是then的参数不是函数,须要忽略它,相似于这种状况:
new Promise(rs => rs(5)) .then() .then(console.log)
其实就是把value和状态日后传递
this.resolveListeners.push((val) => { if (typeof resCb !== 'function') { res(val) return } try { const x = resCb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } }) // rejectListeners也是相同的逻辑
若是调用then时, promise的状态已经肯定,相应的回调直接运行
// 注意这里须要异步 if (status === 'resolved') setTimeout(() => resolveCb(value), 0) if (status === 'rejected') setTimeout(() => rejectCb(value), 0)
最后,就获得了一个完整的then方法,总结一下,then方法干了两件事,一是注册了回调,二是返回一个新的promise对象。
// resolveCb和rejectCb是相同的逻辑,封装成一个函数 const thenCallBack = (cb, res, rej, target, val) => { if (typeof cb !== 'function') { target === 'resolve' ? res(val) : rej(val) return } try { const x = cb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } } Promise.prototype.then = function (resCb, rejCb) { const status = this.status const value = this.value let thenPromise thenPromise = new Promise((res, rej) => { /** * 这里不能使用bind来实现柯里画,规范里规定了: * 2.2.5: onFulfilled and onRejected must be called as functions (i.e. with no this value)) */ const resolveCb = val => { thenCallBack(resCb, res, rej, 'resolve', val) } const rejectCb = val => { thenCallBack(rejCb, res, rej, 'reject', val) } if (status === 'pending') { this.resolveListeners.push(resolveCb) this.rejectListeners.push(rejectCb) } if (status === 'resolved') setTimeout(() => resolveCb(value), 0) if (status === 'rejected') setTimeout(() => rejectCb(value), 0) }) return thenPromise }
首先要明白的是什么叫互相调用,什么状况下会互相调用。以前实现then方法的时候,有一条规则是:若是then方法的回调返回一个promiseA。then返回的promise须要adopt这个promiseA,也就是说,须要处理这种状况:
new MyPromise(rs => rs(5)) .then(val => { return Promise.resolve(5) // 原生Promise }) .then(val => { return new Bluebird(r => r(5)) // Bluebird的promise })
关于这个,规范里定义了一个叫作The Promise Resolution Procedure的过程,咱们须要作的就是把规范翻译一遍,并替代代码中判断promise的地方
const resolveThenable = (promise, x, resolve, reject) => { if (x === promise) { return reject(new TypeError('chain call found')) } if (x instanceof Promise) { return x.then(v => { resolveThenable(promise, v, resolve, reject) }, reject) } if (x === null || (typeof x !== 'object' && typeof x !== 'function')) { return resolve(x) } let called = false try { // 这里有一个有意思的技巧。标准里解释了,若是then是一个getter,那么经过赋值能够保证getter只被触发一次,避免反作用 const then = x.then if (typeof then !== 'function') { return resolve(x) } then.call(x, v => { if (called) return called = true resolveThenable(promise, v, resolve, reject) }, r => { if (called) return called = true reject(r) }) } catch (e) { if (called) return reject(e) } }
到这里,一个符合标准的Promise就完成了,完整的代码以下:
function Promise(executor) { if (typeof executor !== 'function') { throw new Error('Promise executor must be fucntion') } let status = 'pending' let value = void 0 const notify = (target, val) => { target === 'resolved' ? this.resolveListeners.forEach(cb => cb(val)) : this.rejectListeners.forEach(cb => cb(val)) } const resolve = val => { if (val instanceof Promise) { return val.then(resolve, reject) } setTimeout(() => { if (status !== 'pending') return status = 'resolved' value = val notify('resolved', val) }, 0) } const reject = reason => { setTimeout(() => { if (status !== 'pending') return status = 'rejected' value = reason notify('rejected', reason) }, 0) } this.resolveListeners = [] this.rejectListeners = [] Object.defineProperty(this, 'status', { get() { return status }, set() { console.warn('status is read-only') } }) Object.defineProperty(this, 'value', { get() { return value }, set() { console.warn('value is read-only') } }) try { executor(resolve, reject) } catch (e) { reject(e) } } const thenCallBack = (cb, res, rej, target, promise, val) => { if (typeof cb !== 'function') { target === 'resolve' ? res(val) : rej(val) return } try { const x = cb(val) resolveThenable(promise, x, res, rej) } catch (e) { rej(e) } } const resolveThenable = (promise, x, resolve, reject) => { if (x === promise) { return reject(new TypeError('chain call found')) } if (x instanceof Promise) { return x.then(v => { resolveThenable(promise, v, resolve, reject) }, reject) } if (x === null || (typeof x !== 'object' && typeof x !== 'function')) { return resolve(x) } let called = false try { // 这里有一个有意思的技巧。标准里解释了,若是then是一个getter,那么经过赋值能够保证getter只被触发一次,避免反作用 const then = x.then if (typeof then !== 'function') { return resolve(x) } then.call(x, v => { if (called) return called = true resolveThenable(promise, v, resolve, reject) }, r => { if (called) return called = true reject(r) }) } catch (e) { if (called) return reject(e) } } Promise.prototype.then = function (resCb, rejCb) { const status = this.status const value = this.value let thenPromise thenPromise = new Promise((res, rej) => { const resolveCb = val => { thenCallBack(resCb, res, rej, 'resolve', thenPromise, val) } const rejectCb = val => { thenCallBack(rejCb, res, rej, 'reject', thenPromise, val) } if (status === 'pending') { this.resolveListeners.push(resolveCb) this.rejectListeners.push(rejectCb) } if (status === 'resolved') setTimeout(() => resolveCb(value), 0) if (status === 'rejected') setTimeout(() => rejectCb(value), 0) }) return thenPromise }
实现Promise的过程其实并无我预想的那么难,所谓的Promise的原理我感受就是相似于观察者模式,so,不要有畏难情绪,我上我也行^_^。