前端杂谈:如何实现一个 Promise?
首先, 什么是 Promise?
A promise is an object that may produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved (e.g., a network error occurred). A promise may be in one of 3 possible states: fulfilled, rejected, or pending. Promise users can attach callbacks to handle the fulfilled value or the reason for rejection.
关键语句: Promise 是一个在将来某个时刻产生一个单一结果的对象. 通俗一点来说, Promise 代表了一个值, 但是这个值我们并不确定什么时候会被返回.
A promise is an object that may produce a single value some time in the future.
简单看看 Promise 的历史
- Promise 在 1980 年代被创建出来
- 在 1988 年正式得名: Promise
- 已经有很多人了解到了 Promise, 但是人们还是坚持使用 node.js 中提倡的以回调函数首个参数传 error 对象的方式处理异步代码.
- Dojo 首次大规模的使用了 Promise , 相应的 Promise/A 被提出用以规范 Promise 的实现
- JQuery 开始使用 Promise 并真正使 Promise 广为人知
- JQuery 没有实现部分 Promise 的功能, 这也导致了 Promie/A+ 标准的产生
- ES6 正式引入了 Promise,并且和已有的实现了 Promise/A 规范的 library 相兼容
实现 Promise 之前, 让我们看看 Promise 有哪些规范
- Promise 是一个 thenable 对象, 也就是说 Promise 有一个 .then() 方法
- 一个 pending 状态的 Promise 可以进入 fulfilled 和 rejected 状态
- promise 一旦进入 fulfilled 或 rejected 状态, 不可再改变其状态
- 一旦 promise 改变了其状态, 它笔芯有一个值(这个值也可能是 undefined)
开始实现一个 Promise
首先, 让我们看看一段最普通的异步代码:
// 异步方法定义 var basicAsyncFunc = function(callback) { setTimeout(function() { var randomNumber = Math.random() if (randomNumber > 0.5) { callback(null, randomNumber) } else { callback(new Error('bad luck...')) } }, 1000) } // 异步方法调用 basicAsyncFunc((err, result) => { if (err) { console.log(`the reason fail is: ${err}`) return } console.log(`success get result: ${result}`) })
按照 Promise 的规范定义, 理想中 Promise 的调用方式为:
// Promise 形式的异步方法定义 var promiseAsyncFunc = function() {} // Promise 形式的异步方法调用 promiseAsyncFunc.then( data => { console.log(`success get result: ${data}`) }, err => { console.log(`the reason fail is: ${err}`) } )
按照这个理想当中的调用方式, 让我们写出第一版代码.
第一版 Promise:能保存回调方法
// Promise 形式的异步方法定义 var promiseAsyncFunc = function() { var fulfillCallback var rejectCallback setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfillCallback(randomNumber) else rejectCallback(randomNumber) }, 1000) return { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } // Promise 形式的异步方法调用 promiseAsyncFunc().then(fulfillCallback, rejectCallback)
我们的思路是在 .then() 方法中, 将 fullfill 和 reject 结果的回调函数保存下来, 然后在异步方法中调用. 因为是异步调用, 根据 event-loop 的原理, promiseAsyncFunc().then(fulfillCallback, rejectCallback) 传入的 callback 在异步调用结束时一定是已经赋值过了.
第二版 Promise:实构造函数
当前我们的实现 Promise 中,异步逻辑代码和 Promise 的代码是杂糅在一起的,让我们将其区分开:
var promiseAsyncFunc = function() { var fulfillCallback var rejectCallback return { fulfill: function(value) { if (fulfillCallback && typeof fulfillCallback === 'function') { fulfillCallback(value) } }, reject: function(err) { if (rejectCallback && typeof rejectCallback === 'function') { rejectCallback(err) } }, then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } let ownPromise = function(asyncCall) { let promise = promiseAsyncFunc() asyncCall(promise.fulfill, promise.reject) return promise } // Promise 形式的异步方法调用 ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000) })
我们新定义了一个方法 ownPromise() 用于创建 Promise,并在promiseAsyncFunc() 中暴露出 fulfill 和 reject 接口方便异步代码去调用。
这里有一个问题,我们在调用 ownPromise()后得到了 promise 实例,此时我们可以直接调用 fulfill(),reject()这两个方法,而理论上我们应该只应暴露 promise 的then()方法。所以我们利用闭包将这两个方法隐藏:
var promiseAsyncFunc = function() { var fulfillCallback var rejectCallback return { fulfill: function(value) { if (fulfillCallback && typeof fulfillCallback === 'function') { fulfillCallback(value) } }, reject: function(err) { if (rejectCallback && typeof rejectCallback === 'function') { rejectCallback(err) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } } let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() asyncCall(defer.fulfill, defer.reject) return defer.promise } // Promise 形式的异步方法调用 ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000) })
第三版 Promise: 支持状态管理
为了实现规范中对于 Promise 状态变化的要求, 我们需要为 Promise 加入状态管理, 这一步较为简单, 让我们看代码:
const PENDING = Symbol('pending') const FULFILLED = Symbol('fulfilled') const REJECTED = Symbol('rejected') // Promise 形式的异步方法定义 var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback return { fulfill: function(value) { if (status !== PENDING) return if (typeof fulfillCallback === 'function') { fulfillCallback(value) status = FULFILLED } }, reject(error) { if (status !== PENDING) return if (typeof rejectCallback === 'function') { rejectCallback(error) status = REJECTED } }, promise: { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } } let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() asyncCall(defer.fulfill, defer.reject) return defer.promise } // Promise 形式的异步方法调用 ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000) }).then(data => console.log(data), err => console.log(err))
这段代码中我们用到了 Symbol 来表示状态常量, 对 Symbol 不了解的同学可以看这里
为了判断 Promise 的状态, 我们加入了 fulfill 和 reject 两个方法。并在其中判断 promise 当前状态。如果不是 pending 状态则直接 return(因为 Promise 状态只可能改变一次)。
现在我们的 promise 实现了对状态控制的规范:
- 只允许改变一次状态
- 只能从 pending => fulfilled 或 pending => rejected
但是我们的 Promise 有一个问题: promise 的值没有被保存下来。如果 promise 在异步调用完成之后才被调用 .then()方法,则我们无法把异步调用的结果传递给回调函数。为此我们需要为 Promise 加一个 value 字段:
第四版 Promise: 保存异步调用的结果
我们为 promise 加入 value 字段,用于保存 Promise 的执行结果。
// Promise 形式的异步方法定义 var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = _value status = FULFILLED if (typeof fulfillCallback === 'function') { fulfillCallback(value) } }, reject(error) { if (status !== PENDING) return value = error status = REJECTED if (typeof rejectCallback === 'function') { rejectCallback(error) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } }
这里我们又发现一个问题,如果一个 Promise 已经是fulfill或reject状态。我们再调用 then() 方法时,传入的回调方法永远不会被调用(因为 status 已经不是 pending)。
所以我们需要在 then()方法中对其状态进行判断:
// Promise 形式的异步方法定义 var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = _value status = FULFILLED if (typeof fulfillCallback === 'function') { fulfillCallback(value) } }, reject(error) { if (status !== PENDING) return value = error status = REJECTED if (typeof rejectCallback === 'function') { rejectCallback(error) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { if (status === REJECTED) { _rejectCallback(value) return } if (status === FULFILLED) { _fulfillCallback(value) return } fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } } }
第五版 Promise: 支持链式调用
为了支持链式调用,.then() 方法的返回值必须是用 thenable (根据 Promise/A+ 规范, .then() 方法的返回值需要是一个新的 Promise)
为此我们加入一个工具方法 makeThenable()。如果传入的 value 本身就有 then()方法,则直接返回 value。否则返回一个有 then()方法的对象。 在该对象的 then()方法中,我们根据 promise 的状态,调用不同的回调方法生成新的 value。
function makeThenable(value, status){ if(value && typeof value.then === 'function'){ return value } if(status === FULFILLED){ return { then: function(fulfillCallback, rejectCallback){ return makeThenable(fulfillCallback(value), FULFILLED) } } } if(status === REJECTED) { return { then: function(fulfillCallback, rejectCallback){ return makeThenable(rejectCallback(value), FULFILLED) } } } }
有了以上的 makeThenable()方法,我们可以在 promise 的fulfill(),reject()回将 value 设置为 thenable:
var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = makeThenable(_value, FULFILLED) // 保证当前promise的value为 thenable status = FULFILLED if (typeof fulfillCallback === 'function') { value.then(fulfillCallback) } }, reject(error) { if (status !== PENDING) return value = makeThenable(error, REJECTED) 、、 // 保证当前value为 thenable status = REJECTED if (typeof rejectCallback === 'function') { value.then(null, rejectCallback) } }, promise: { then: function(){} } } }
接下来让我们看 then()方法。为了返回一个新的 promise,我们首先得创建一个新的 promise。其次当前 promise 在fulfill() 或 reject()时,应该调用新的 promise 的fullfill() 或 reject()方法。所以我们在将 fulfullCallback和rejectCallback赋值给当前 promise 时,将其包装一下。代码如下:
promise: { then: function(_fulfillCallback, _rejectCallback) { let newPromiseAsyncFunc = promiseAsyncFunc() let fulfillFunc = function(value) { newPromiseAsyncFunc.fulfill(_fulfillCallback(value)) } let rejectFunc = function(err) { newPromiseAsyncFunc.fulfill(_rejectCallback(err)) } if (status === PENDING) { fulfillCallback = fulfillFunc rejectCallback = rejectFunc } else { value.then(fulfillFunc, rejectFunc) } return newPromiseAsyncFunc.promise } }
如此,我们变得到了一个可以链式调用的 promise。让我们来测试一下:
const PENDING = Symbol('pending') const FULFILLED = Symbol('fulfilled') const REJECTED = Symbol('rejected') function makeThenable(value, status) { if (value && typeof value.then === 'function') { return value } if (status === FULFILLED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(fulfillCallback(value), FULFILLED) } } } if (status === REJECTED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(rejectCallback(value), FULFILLED) } } } } // Promise 形式的异步方法定义 var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = makeThenable(_value, FULFILLED) status = FULFILLED if (typeof fulfillCallback === 'function') { value.then(fulfillCallback) } }, reject(error) { if (status !== PENDING) return value = makeThenable(error, REJECTED) status = REJECTED if (typeof rejectCallback === 'function') { value.then(null, rejectCallback) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { let newPromiseAsyncFunc = promiseAsyncFunc() let fulfillFunc = function(value) { newPromiseAsyncFunc.fulfill(_fulfillCallback(value)) } let rejectFunc = function(err) { newPromiseAsyncFunc.fulfill(_rejectCallback(err)) } if (status === PENDING) { fulfillCallback = fulfillFunc rejectCallback = rejectFunc } else { value.then(fulfillFunc, rejectFunc) } return newPromiseAsyncFunc.promise } } } } let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() asyncCall(defer.fulfill, defer.reject) return defer.promise } let testChainedPromise = ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000) }) .then( data => { console.log(data) return 'return value in then1 fulfill' }, err => { console.log(err) return 'return value in then1 reject' } ) .then( data => { console.log(data) return 'return value in then2 fulfill' }, err => { console.log(err) return 'return value in then2 reject' } ) .then( data => { console.log(data) }, err => { console.log(err) } ) /** console output: 0.9931984611850693 return value in then1 fulfill return value in then2 fulfill */
第六版 Promise: Error handling
这里我们只对异步调用和fulfill 回调中抛出的 error 进行处理。
首先是异步调用部分,我们将其 try catch 起来,在发生异常时调用 reject 方法,并将异常作为参数传入。
let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() try { asyncCall(defer.fulfill, defer.reject) } catch (e) { defer.reject(e) } return defer.promise }
然后是 fulfill 中可能出现的异常。我们对fulfillCallback(value)可能出现的异常进行捕获,并将异常传递给rejectCallback。
function makeThenable(value, status) { if (value && typeof value.then === 'function') { return value } if (status === FULFILLED) { return { then: function(fulfillCallback, rejectCallback) { try { let newValue = fulfillCallback(value) return makeThenable(newValue, FULFILLED) } catch (e) { return makeThenable(rejectCallback(e), FULFILLED) } } } } if (status === REJECTED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(rejectCallback(value), FULFILLED) } } } }
最后让我们对完整的代码进行测试:
const PENDING = Symbol('pending') const FULFILLED = Symbol('fulfilled') const REJECTED = Symbol('rejected') function makeThenable(value, status) { if (value && typeof value.then === 'function') { return value } if (status === FULFILLED) { return { then: function(fulfillCallback, rejectCallback) { try { let newValue = fulfillCallback(value) return makeThenable(newValue, FULFILLED) } catch (e) { return makeThenable(rejectCallback(e), FULFILLED) } } } } if (status === REJECTED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(rejectCallback(value), FULFILLED) } } } } // Promise 形式的异步方法定义 var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = makeThenable(_value, FULFILLED) status = FULFILLED if (typeof fulfillCallback === 'function') { value.then(fulfillCallback) } }, reject(error) { if (status !== PENDING) return value = makeThenable(error, REJECTED) if (typeof rejectCallback === 'function') { value.then(null, rejectCallback) } status = REJECTED }, promise: { then: function(_fulfillCallback, _rejectCallback) { let newPromiseAsyncFunc = promiseAsyncFunc() let fulfillFunc = function(value) { newPromiseAsyncFunc.fulfill(_fulfillCallback(value)) } let rejectFunc = function(err) { newPromiseAsyncFunc.fulfill(_rejectCallback(err)) } if (status === PENDING) { fulfillCallback = fulfillFunc rejectCallback = rejectFunc } else { value.then(fulfillFunc, rejectFunc) } return newPromiseAsyncFunc.promise } } } } let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() try { asyncCall(defer.fulfill, defer.reject) } catch (e) { defer.reject(e) } return defer.promise } let testChainedPromise = ownPromise(function(fulfill, reject) { throw Error('here is an error in asyncCall') setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000) }) .then( data => { console.log(data) return 'return value in then1 fulfill' }, err => { console.log(err.message) return 'return value in then1 reject' } ) .then( data => { console.log(data) throw Error('here is an error in fulfill1') return 'return value in then2 fulfill' }, err => { console.log(err.message) return 'return value in then2 reject' } ) .then( data => { console.log(data) }, err => { console.log(err.message) } ) // console out: Error: here is an error in asyncCall return value in then1 reject Error: here is an error in fulfill1 return value in then2 reject
总结
以上就是我们对于 Promise 的一个简单的实现,实现思路主要参考了 Q-A promise library for javascript。该实现的 Promise 功能较为简陋,仅实现了部分 api/规范。
原创作者:ssthouse_hust
https://my.oschina.net/u/3413999/blog/2988091