开头
手写了一份通过标准测试的Promise源码,以它为例,详解怎么写出来,细到每一行代码。
Promises/A+
Promise的功能和特性,请参考promise/A+标准。
1
function Promise(executor) {
this.status = 'pending'; // pending, fulfilled, rejected
this.value = null;
this.handlers = [];
this._doResolve(executor)
}
- Promise是一个构造函数,接收一个函数作为参数
- status记录的是Promise的状态
- value记录的是Promise的值
- handlers是当前Promise状态变化后要执行的函数,这些函数是通过then方法添加进来。每一项数据结构为:
{
resolve: onFulfilled,
reject: onRejected,
promise: thenPromise,
}
resolve和reject是因为Promise有两种可能的结果,对应不同函数。promise记录的是新的Promise,用于then方法将promise传递下去。
ps. this.handlers能改成对象吗,只有一项,答案是不行的。虽然每次then会返回新的Promise,但是同一个Promise是能添加两次then方法的。比如:
var promise1 = new Promise(function(resolve, reject) {
resolve(2)
})
promise1.then(function(value) {
console.log(value + 1)
})
promise1.then(function(value) {
console.log(value + 2)
})
这种情况下同一个Promise使用了两次的then方法,故handlers需要时数组,才能记录。
- this._doResolve(executor) 这一行是整个Promise的启动行。因为Promise接收的是一个函数,必须调用它才能开启整个流程。如下图,Promise须自动调用function(resolve, reject){ resolve(2) }这个函数。
new Promise(function(resolve, reject) {
resolve(2)
})
2
Promise.prototype = {
constructor: Promise,
_doResolve: function (fn) {
var done = false, self = this;
try {
fn(function (value) {
if (done) return
done = true;
self.resolve(value)
}, function (reason) {
if (done) return
done = true;
self.reject(reason)
})
} catch (e) {
if (done) return
done = true;
self.reject(e)
}
}
}
- _doResolve的参数fn就是new Promise时传进来的函数
- 使用done是为了保证这个函数只执行一次
- 使用try...catch是因为:函数作为参数,由外面传递过来,那么函数是否符合语法无法保证。故「执行作为参数的函数」,都应该添加try...catch。如果错误,Promise状态变成rejected。
- fn两个参数,也是函数。根据Promise执行结果,启动内部流程。每次新建Promise都需要手动触发。
new Promise(function(resolve, reject) {
resolve(2) // 这里就会调用fn函数第一个参数,启动内部的self.resolve(value)流程
})
3
Promise.prototype = {
...
then: function (onFulfilled, onRejected) {
var self = this;
var thenPromise = new Promise(function () { });
if (self.status === 'pending') {
self.handlers.push({
promise: thenPromise,
resolve: onFulfilled,
reject: onRejected
})
} else {
self._handle(thenPromise, onFulfilled, onRejected);
}
return thenPromise
},
}
- then必须返回一个新的Promise,故有thenPromise
- then添加的是接下来执行的函数,有两种可能onFulfilled和onRejected;与thenPromise组成一项添加到数组handlers(将过段时间才执行的函数用数组先储存,到时再取,是一种常用方法)
- status有两种情况,一种是pending,另外一种是「fulfilled或rejected中一种」。故分情况pending添加到handlers,否则就直接执行。
下面这种情况,第一个then函数执行时,Promise的status是fulfilled,第二个then函数的Promise的status是pending。因为第一个Promise已由resolve(2)将状态改成fulfilled;而第二个Promise添加then函数时,还没有执行resolve(也就是内部执行resolve是异步,目的是为了防止执行顺序出错,后面会讲)。
new Promise(function(resolve, reject) {
resolve(2)
}).then(function(value) {
console.log(33, value)
return 5
}).then(function(value) {
console.log(44, value)
})
4
Promise.prototype = {
...
resolve: function (result) {
var self = this;
if (result === self) {
throw new TypeError('Promise can not resolved by itself');
}
try {
var then = self._getThen(result);
if (then) {
self._doResolve(then.bind(result))
return
}
self.status = 'fulfilled';
self.value = result;
self._dequeue();
} catch (e) {
self.reject(e)
}
},
reject: function (reason) {
var self = this;
self.status = 'rejected';
self.value = reason;
self._dequeue()
},
}
- resolve和reject都是改变Promise状态和赋予Promise值,然后启动_dequeue(实际就是调用handlers函数)
- resolve多了两个判断,一是返回的值不能是Promise自己;二是返回的值是Promise,需要特殊处理
- _getThen方法通过鸭子辨证法判断返回值是否是Promise,如果是返回这个子Promise的then值;
鸭子辨证法是一种证明方法:当一个事物具有鸭子的特性,那我们就认为它是鸭子。当一个对象或函数具有then方法,那么我们就认为它是一个Promise。鸭子辨证法有可能出错,但是绝大部分情况下是可行的。代码如下:
Promise.prototype = {
...
_getThen: function (result) {
var t = typeof result;
if (result && (t === 'object' || t === 'function')) {
var then = result.then;
if (typeof then === 'function') {
return then
}
}
return null
},
}
接下来三行代码,是整个Promise源码最难理解的代码,解决的问题是如果返回的是子Promise,那么该如何继续这个流程:
if (then) {
self._doResolve(then.bind(result))
return
}
- 流程应该return掉,因为子Promise还没有结束,当前Promise状态是不能改变
- 当前流程应该重新开始,因为原先流程已经被子Promise打断了,这一次self._doResolve的是子Promise的结果
- 子Promise的结果在哪里呢,在子Promise的then函数中,因为then函数存储的就是Promise的执行结果
- then.bind(result),将then绑定到子Promise中,确保执行的是子Promise返回的结果;使用bind而不是call或apply是因为bind返回一个新的函数
- self._doResolve会执行then函数,并将「function (value) { if (done) return; done = true; self.resolve(value) }」当成onFulfilled传递给then
简单来说:当Promise遇到值为子Promise,那么就重新启动Promise,并把子Promise的执行结果(也就是子Promise的then函数)拿过来执行。相当于一条绳子,剪断,再加一段把两边合起来。
5
Promise.prototype = {
...
_dequeue: function () {
var self = this;
var handle;
while (self.handlers.length) {
handle = self.handlers.shift();
self._handle(handle.promise, handle.resolve, handle.reject)
}
},
_handle: function (thenPromise, onFulfilled, onRejected) {
var self = this;
setTimeout(function () {
var callback = self.status == 'fulfilled' ? onFulfilled : onRejected
if (typeof callback === 'function') {
try {
const result = callback(self.value)
thenPromise.resolve(result)
} catch (e) {
thenPromise.reject(e)
}
return
}
self.status == 'fulfilled' ? thenPromise.resolve(self.value) : thenPromise.reject(self.value)
}, 0)
},
}
- _dequeue实际上是批量执行handlers里面函数
- _handle中,由于then还可以传函数之外的值,故需要判断是否是函数。如下,then(5)也是不会报错的。
new Promise(function(resolve, reject) {
resolve(2)
}).then(5).then(function(){
console.log(3)
})
- thenPromise.resolve(result) 用thenPromise开启下一次循环,此功能类似于上面例子的resolve(2),只是第一次要手动触发,接下来每一个由Promise自动触发。完成链式调用。
- 使用setTimeout(function(){}, 0),是为了保证then的参数--函数都是异步执行,防止出现函数执行顺序不确定情况:如下图,如果then是同步执行,那么bar和foo谁先执行不确定;但如果是异步执行,那么foo肯定先执行
new Promise.then(function(){
if (true) {
// 同步执行
bar();
} else {
// 异步执行 (如:使用第三方库)
setTimeout(function(){
bar();
})
}
});
foo();
总结
从源码实现的角度:Promise相当于一个中间层,代理了函数和回调函数。用status表示状态,用value表示值;用handlers存储回调;用_doResolve开启流程,并做成功回调和失败回调之分;用then添加回调函数,返回新的Promise;执行完handlers中函数,又重新开启resolve,形成链式调用。
以下为全部代码:
function Promise(executor) {
this.status = 'pending';
this.value = null;
this.handlers = [];
this._doResolve(executor)
}
Promise.prototype = {
constructor: Promise,
_doResolve: function (fn) {
var done = false, self = this;
try {
fn(function (value) {
if (done) return
done = true;
self.resolve(value)
}, function (reason) {
if (done) return
done = true;
self.reject(reason)
})
} catch (e) {
if (done) return
done = true;
self.reject(e)
}
},
_getThen: function (result) {
var t = typeof result;
if (result && (t === 'object' || t === 'function')) {
var then = result.then;
if (typeof then === 'function') {
return then
}
}
return null
},
_dequeue: function () {
var self = this;
var handle;
while (self.handlers.length) {
handle = self.handlers.shift();
self._handle(handle.promise, handle.resolve, handle.reject)
}
},
_handle: function (thenPromise, onFulfilled, onRejected) {
var self = this;
setTimeout(function () {
var callback = self.status == 'fulfilled' ? onFulfilled : onRejected
if (typeof callback === 'function') {
try {
const result = callback(self.value)
thenPromise.resolve(result)
} catch (e) {
thenPromise.reject(e)
}
return
}
self.status == 'fulfilled' ? thenPromise.resolve(self.value) : thenPromise.reject(self.value)
}, 0)
},
resolve: function (result) {
var self = this;
if (result === self) {
throw new TypeError('Promise can not resolved by itself');
}
try {
var then = self._getThen(result);
if (then) {
self._doResolve(then.bind(result))
return
}
self.status = 'fulfilled';
self.value = result;
self._dequeue();
} catch (e) {
self.reject(e)
}
},
reject: function (reason) {
var self = this;
self.status = 'rejected';
self.value = reason;
self._dequeue()
},
then: function (onFulfilled, onRejected) {
var self = this;
var thenPromise = new Promise(function () { });
if (self.status === 'pending') {
self.handlers.push({
promise: thenPromise,
resolve: onFulfilled,
reject: onRejected
})
} else {
self._handle(thenPromise, onFulfilled, onRejected);
}
return thenPromise
},
all: function (promises) {
if (!Array.isArray(promises)) {
throw new TypeError('arguments must be an array')
}
return new Promise(function (resolve, reject) {
let resolvedCounter = 0;
const promiseNum = promises.length
let resolvedValues = new Array(promiseNum);
for (let i = 0; i < promiseNum; i++) { //必须用let 或者在外围加(function(i){})(i),形成闭包
promises[i].then((value) => {
resolvedCounter++
resolvedValues[i] = value
if (resolvedCounter === promiseNum) {
return resolve(resolvedValues)
}
}, (reason) => {
return reject(reason)
})
}
})
},
race: function (promises) {
if (!Array.isArray(promises)) {
throw new TypeError('arguments must be an array')
}
return new Promise(function (resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then((value) => {
return resolve(value)
}, (reason) => {
return reject(reason)
})
}
})
},
}
Promise.deferred = Promise.defer = function () {
var dfd = {}
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = Promise;