手写Promise-Promise\A+测试
接下来咱们来测试一下手写Promise的代码能否通过Promise\A+测试
Promise\A+规范:
Promise\A+是社区推出的规范,其实最早Promise也是社区推出并实现的,旨在规范Promise的实现,里面约定了:
- 状态必须是:pending,fulfilled,rejected
- then方法的详细实现细节
- …
早期使用Promise可能需要导入一些库,比如:
- Q
- when
- WinJS
- RSVP.js
现在已经不需要了,因为在ES6中已经加入语言标准,我们可以直接使用了:从 Chrome 32、Opera 19、Firefox 29、Safari 8 和 Microsoft Edge 开始,promises 是默认开启的。若要使缺乏完整 promise 实现的浏览器符合规范,或将 promise 添加到其他浏览器和 Node.js 中,请查看polyfill (2k gzipped)。
上面提到的库,以及ES6中实现的Promise,还有我们手写的Promise其实都是按照标准进行编写的,那么如何测试是否符合标准呢
测试:
社区提供了promises-aplus-tests用来测试实现的Promise是否符合规范,使用方式为:
-
使用CommonJS的方式暴露对象,要求如下
1. 提供deferred方法,返回对象{promise,resolve,reject} 1.1 promise: pending状态的promise实例(自己手写的Promise) 1.2 resolve: 以传入的原因兑现promise 1.3 reject: 以传入的原因拒绝promise
// 将我们自己手写的Promise拷贝到一个单独的文件,并在底部加上 module.exports = { deferred() { const res = {} // 自己手写的Promise res.promise = new HMPromise((resolve, reject) => { // 内部将resolve和reject赋值上去 res.resolve = resolve res.reject = reject }) return res } }
-
下包:
- 初始化项目:
npm init -y
- 下包:
npm i promises-aplus-tests -D
- 初始化项目:
-
配置并执行命令:
-
package.json
的scripts
中加入 -
注:
HMPromise
是文件名,根据实际情况调整自己的文件名即可"test": "promises-aplus-tests HMPromise"
-
执行命令:
npm run test
-
测试:
我们目前的写法中,没有考虑所有的边界情况,测试时会在2.3.3开始出错
只需要将resolvePromise
函数替换为如下写法即可:
- 函数名,参数顺序和原函数一致
- 函数内部使用,序号+说明的方式对Promise\A+的标准进行标注
大伙可以参考注释对比确认还需要考虑哪些便捷情况,
// 符合Promise\A规范(考虑了各种边界情况)
function resolvePromise(p2, x, resolve, reject) {
// 2.3.3.1 如果p2和x引用同一个对象,通过TypeError作为原因来拒绝pormise
if (x === p2) {
throw new TypeError('Chaining cycle detected for promise');
}
/**
* 2.3.3.2 如果x是一个promise,采用他的状态
* 2.3.3.3.1 如果x是pengding状态,promise必须保持等待状态,直到x被fulfilled或rejected
* 2.3.3.3.2 如果x是fulfilled状态,用相同的原因解决promise
* 2.3.3.3.3 如果x是rejected状态,用相同的原因拒绝promise
* */
if (x instanceof HMPromise) {
x.then(y => {
resolvePromise(p2, y, resolve, reject)
}, reject);
}
// 2.3.3 如果x是一个对象或者函数
else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) {
// 2.3.3.1 让then成为x.then
try {
var then = x.then;
} catch (e) {
// 2.3.3.2 如果检索属性x.then抛出了异常e,用e作为原因拒绝promise
return reject(e);
}
/**
* 2.3.3.3 如果then是一个函数,通过call调用他,并且将x作为他的this(参数1)
* 调用then时传入2个回调函数:
* 第一个参数叫做resolvePromise(对应到的参数2)
* 第二个参数叫做rejectPromise(对应到参数3)
* */
if (typeof then === 'function') {
// 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者同一参数被调用了多次,只采用第一次调用,后续的调用会被忽略(观察called后续的赋值+判断)
let called = false;
try {
then.call(
x,
// 2.3.3.3.1 如果 resolvePromise 以 成功原因 y 为参数被调用,继续执行 resolvePromise
y => {
if (called) return;
called = true;
resolvePromise(p2, y, resolve, reject);
},
// 2.3.3.3.2 如果 rejectPromise 以拒绝原因 r 为参数被调用,用 r 拒绝 promise
r => {
if (called) return;
called = true;
reject(r);
}
)
}
// 2.3.3.3.4 如果调用then抛出异常
catch (e) {
// 2.3.3.3.4.1 如果resolvePromise或rejectPromise已经被调用,忽略它
if (called) return;
called = true;
// 2.3.3.3.4.2 否则以 e 作为拒绝原因 拒绝promise
reject(e);
}
} else {
// 2.3.3.4 如果then不是函数,用 x 作为原因 兑现promise
resolve(x);
}
} else {
// 2.3.4 如果then不是对象或函数,用 x 作为原因 兑现promise
return resolve(x);
}
}
替换完毕之后,再次执行npm run test
,全部测试通过.
面试回答:
手写Promise-Promise\A+测试
Promise
和Promise\A+
规范的关系
Promise\A+
是社区推出的规范,最早Promise也是社区推出并实现的,旨在规范Promise
的实现,里面约定了:- 状态必须是
pending
,fulfilled
,rejected
then
方法的详细实现细节- 各种边界情况…
- 状态必须是
- 早期使用
Promise
需要导入第三方库,现在在新式浏览器中已经不需要导入第三方库,因为Promise
是默认开启的 - 无论是早期实现了Promise的第三方库,以及现在的新式浏览器内置的Promise,都是符合
Promise\A+
规范要求的
参考资料
- MDN-Promise