准备工作
- Promises/A+规范:promisesaplus.com (中文翻译可参考这篇博客)
- 完整测试用例:github.com/promises-ap…
开始
首先,抛开测试用例,实现一个我们印象中的Promise
-
Promise是一个构造函数,其入参接收一个函数 (fn),
-
Promise执行会调用fn, 并将resolve和reject作为入参传递给fn (resolve和reject均是函数)
-
Promise执行后,返回其实例promise
-
promise有三种状态:pending、fullfilled、rejected
-
promise存在then方法,then方法可以注册回调函数onFullfilled、onRejeted,then方法返回Promise实例 (nextPromise) ,供链式调用
- then方法内部:当promise为非pending状态时,执行回调。 (
回调的执行结果会对nextPromise产生影响,所以每次注册的回调和对应的nextPromise需要一起保存
)
- then方法内部:当promise为非pending状态时,执行回调。 (
-
promise默认pending状态
- 执行resolve置为fullfilled,保存resolve入参 (data),触发onFullfilled
- 执行reject置为rejected,保存reject入参 (data),触发onRejeted
-
执行callback时 (onFullfilled、onRejeted) ,传入保存的data
- callback正常返回:触发nextPromise的resolve,传入返回值
- callback执行捕获到错误:触发nextPromise的reject,传入捕获到的错误信息
-
若callback的返回值是Promise实例,保证nextPromise的状态与返回的promise的状态同步
function execCallback(promise) {
const defers = promise.defers;
while (defers.length > 0) {
const defer = defers.shift();
const nextPromise = defer.promise;
const callback = promise.state === 'fullfilled' ? defer.onFullfilled : defer.onRejected;
let cbRes;
try {
cbRes = callback(promise.data);
} catch (err) {
reject(nextPromise, err);
continue;
}
if (cbRes instanceof Promise) {
cbRes.then(data => { // 当cbRes也是Promise时,保证nextPromise的状态与cbRes一致
resolve(nextPromise, data);
}, err => {
reject(nextPromise, err);
});
} else {
resolve(nextPromise, cbRes);
}
}
}
function resolve(promise, data) {
promise.data = data;
promise.state = 'fullfilled';
execCallback(promise);
}
function reject(promise, err) {
promise.data = err;
promise.state = 'rejected';
execCallback(promise);
}
function Promise(fn) {
this.state = 'pending'; // pending|fullfilled|rejected
this.data = undefined;
this.defers = []; // 保存 callback 和 nextPromise
const promise = this;
fn(data => {
resolve(promise, data);
}, err => {
reject(promise, err);
});
return this;
};
Promise.prototype.then = function(onFullfilled, onRejected) {
const nextPromise = new Promise(function() {});
let defer = {
promise: nextPromise,
onFullfilled,
onRejected
}; // 回调的执行会对nextPromise产生影响,故一起保存
this.defers.push(defer)
if (this.state !== 'pending') {
execCallback(this); // 非pending状态,触发callback
}
return nextPromise;
}
module.exports = Promise;
复制代码
用几个常用的promise demo验证了下上述代码,没发现问题
跑测试用例,结果是:
171 passing (2m)
672 failing
1) 2.2.1: Both `onFulfilled` and `onRejected` are optional arguments. 2.2.1.2: If `onRejected` is not a function, it must be ignored. applied to a promise fulfilled and then chained off of `onFulfilled` is `undefined`:
Error: timeout of 200ms exceeded. Ensure the done() callback is being called in this test.
......
复制代码
分析log,按顺序先实现规范2.2.1:处理callback不为函数的情况
2.2.1 Both onFulfilled and onRejected are optional arguments:
2.2.1.1. If onFulfilled is not a function, it must be ignored.
2.2.1.2. If onRejected is not a function, it must be ignored.
意思是若onFulfilled、onRejected不是函数,则忽略。
考虑如下case:
new Promise((resolve, reject) => {
resolve(123);
})
.then(null, null)
.then(data => {
console.log('data: ', data)
}, err => {
console.log('error: ', err)
})
复制代码
代码修改如下:
(^_^本文写到后面发现这儿有处错误: 第8行不应该使用return,应该使用continue,但是测试用例还是过了。¶¶¶)实现规范2.2.2、2.2.3、2.2.4中未通过的部分:异步执行callback
2.2.2. If onFulfilled is a function:
2.2.2.2. it must not be called before promise is fulfilled.
2.2.3. If onRejected is a function,
2.2.3.2. it must not be called before promise is rejected.
2.2.4. onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].
这几条规范总结起来就是: callback需要异步执行
所以为execCallback的方法体加上setTimeout就能解决问题
实现规范2.1.2与2.1.3:状态转换的限制
2.1.2. When fulfilled, a promise:
2.1.2.1. must not transition to any other state.
2.1.2.2. must have a value, which must not change.
2.1.3. When rejected, a promise:
2.1.3.1. must not transition to any other state.
2.1.3.2. must have a reason, which must not change.
状态只能从pending转掉fullfilled或者rejected, 且只能转换一次
改动如下:
查看diff至此,规范1、2.1、2.2的要求全部满足
理解规范2.3:callback返回promise & resolve传入promise的处理
promise的callback函数是可以返回promise的,
对于返回promise的情况,then方法返回的newPromise的状态和携带的数据,要求与callback返回的promise一致,看demo:
const promise1 = new Promise((resolve, reject) => {
resolve(123);
});
const promise2 = new Promise((resolve, reject) => {
resolve(456);
});
new Promise((resolve, reject) => {
resolve(promise1);
}).then(data => {
console.log(data) // expect "123"
return promise2;
}).then(data => {
console.log(data); // expect "456"
})
复制代码
规范2.3:
- 保障了promise状态和数据的链式传递,实现了异步代码的继发调用,避免callback hell
- 规定了只要对象是
thenable
,就可以当成promise处理
关于thenable
, 简单来讲,就是包含then方法的对象或函数:
new Promise((resolve, reject) => {
resolve('ok');
}).then(data => {
return { // 此处返回的就是thenable
then(resolve, reject) {
resolve('aaa');
}
};
}).then(data => {
console.log(data) // log: aaa
})
复制代码
为什么会有thenable
这个定义?
主要是为了使不同的promise实现可以协同使用
例如,张三实现了个Promise1, 李四实现了个库Promise2,那么可以这么使用
new Promise1(resolve => {
resolve(new Promise2())
})
复制代码
具体到实现代码,不可通过跑一遍测试用例判断是否Promise, 所以规定通过thenable判断是否可以协同使用
实现规范 2.3
前文的测试log,报错指向 2.3.1
| 2.3.1. promise and x refer to the same object, reject promise with a TypeError as the reason.
case如下
let promise = new Promise((resolve, reject) => {
resolve(123);
});
let nextPromise = promise.then(data => {
return nextPromise; // 这种情况需要throw TypeError
})
复制代码
代码改动:
查看diff2.3.3 & 2.3.4
其实就是围绕thanable
的一堆处理逻辑,先大致按照文档写一下
注意:
- execCallback和resolve函数都要处理thenable
- promise也可以当成thenable处理
这里先贴一下用于处理thenable的函数:
/**
* 处理thenanble, 如果data不是thenable,返回false
* @param promise: thanable执行后会对该promis产生影响
* @param data: callback的返回,或者resolve的传入值
*/
function doResolveThenable (promise, data) {
if (data && /object|function/.test(typeof data)) {
let then;
try {
then = data.then;
if (typeof then !== 'function') return false; // 非thenanble
then.call(data, data => {
resolve(promise, data)
}, err => {
reject(promise, err)
})
} catch(err) {
reject(promise, err)
}
} else {
return false; // 非thenanble
}
return true;
}
复制代码
其它修改
查看diff再看log,只剩下60个失败的case了
剩下的case如下:
thenable的then方法里,如果先后执行了onFullfilled、onRejected、throw,应当以第一次执行的为准,忽略后续的执行
let promise = new Promise((resolve, reject) => {
resolve('ok');
}).then(data => {
return {
then(onFullfilled, onRejected) {
onFullfilled(111); // 只允许这一句执行
onFullfilled(222);
onRejected(333);
onRejected(444);
throw (666);
}
}
})
复制代码
代码修改:
查看diff终于,测试用例全过了,大功告成~~~