Promise的源码实现(完美符合PromiseA+规范)
0 条评论Promise的源码实现(完美符合PromiseA+规范) 原文:https://github.com/YvetteLau/Blog/issues/2
Promise是前端面试中的高频问题,我作为面试官的时候,问Promise的概率超过90%,据我所知,大多数公司,都会问一些关于Promise的问题。如果你能根据PromiseA+的规范,写出符合规范的源码,那么我想,对于面试中的Promise相关的问题,都能够给出比较完美的答案。
我的建议是,对照规范多写几次实现,也许第一遍的时候,是改了多次,才能通过测试,那么需要反复的写,我已经将Promise的源码实现写了不下七遍。
Promise的源码实现
1 | /** |
有专门的测试脚本可以测试所编写的代码是否符合PromiseA+的规范。
首先,在promise实现的代码中,增加以下代码:
1 | Promise.defer = Promise.deferred = function () { |
安装测试脚本:
1 | npm install -g promises-aplus-tests |
如果当前的promise源码的文件名为promise.js
那么在对应的目录执行以下命令:
1 | promises-aplus-tests promise.js |
promises-aplus-tests中共有872条测试用例。以上代码,可以完美通过所有用例。
对上面的代码实现做一点简要说明(其它一些内容注释中已经写得很清楚):
onFulfilled 和 onFulfilled的调用需要放在setTimeout,因为规范中表示: onFulfilled or onRejected must not be called until the execution context stack contains only platform code。使用setTimeout只是模拟异步,原生Promise并非是这样实现的。
在 resolvePromise 的函数中,为何需要usedd这个flag,同样是因为规范中明确表示: If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored. 因此我们需要这样的flag来确保只会执行一次。
self.onFulfilled 和 self.onRejected 中存储了成功的回调和失败的回调,根据规范2.6显示,当promise从pending态改变的时候,需要按照顺序去指定then对应的回调。
PromiseA+的规范(翻译版)
PS: 下面是我翻译的规范,供参考
术语
- promise 是一个有then方法的对象或者是函数,行为遵循本规范
- thenable 是一个有then方法的对象或者是函数
- value 是promise状态成功时的值,包括 undefined/thenable或者是 promise
- exception 是一个使用throw抛出的异常值
- reason 是promise状态失败时的值
要求
2.1 Promise States
Promise 必须处于以下三个状态之一: pending, fulfilled 或者是 rejected
2.1.1 如果promise在pending状态
2.1.1.1 可以变成 fulfilled 或者是 rejected
2.1.2 如果promise在fulfilled状态
2.1.2.1 不会变成其它状态
2.1.2.2 必须有一个value值
2.1.3 如果promise在rejected状态
2.1.3.1 不会变成其它状态
2.1.3.2 必须有一个promise被reject的reason
概括即是:promise的状态只能从pending变成fulfilled,或者从pending变成rejected.promise成功,有成功的value.promise失败的话,有失败的原因
2.2 then方法
promise必须提供一个then方法,来访问最终的结果
promise的then方法接收两个参数
promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled 和 onRejected 都是可选参数
2.2.1.1 onFulfilled 必须是函数类型
2.2.1.2 onRejected 必须是函数类型
2.2.2 如果 onFulfilled 是函数:
2.2.2.1 必须在promise变成 fulfilled 时,调用 onFulfilled,参数是promise的value
2.2.2.2 在promise的状态不是 fulfilled 之前,不能调用
2.2.2.3 onFulfilled 只能被调用一次
2.2.3 如果 onRejected 是函数:
2.2.3.1 必须在promise变成 rejected 时,调用 onRejected,参数是promise的reason
2.2.3.2 在promise的状态不是 rejected 之前,不能调用
2.2.3.3 onRejected 只能被调用一次
2.2.4 onFulfilled 和 onRejected 应该是微任务
2.2.5 onFulfilled 和 onRejected 必须作为函数被调用
2.2.6 then方法可能被多次调用
2.2.6.1 如果promise变成了 fulfilled态,所有的onFulfilled回调都需要按照then的顺序执行
2.2.6.2 如果promise变成了 rejected态,所有的onRejected回调都需要按照then的顺序执行
2.2.7 then必须返回一个promise
promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1 onFulfilled 或 onRejected 执行的结果为x,调用 resolvePromise
2.2.7.2 如果 onFulfilled 或者 onRejected 执行时抛出异常e,promise2需要被reject
2.2.7.3 如果 onFulfilled 不是一个函数,promise2 以promise1的值fulfilled
2.2.7.4 如果 onRejected 不是一个函数,promise2 以promise1的reason rejected
2.3 resolvePromise
resolvePromise(promise2, x, resolve, reject)
2.3.1 如果 promise2 和 x 相等,那么 reject promise with a TypeError
2.3.2 如果 x 是一个 promsie
2.3.2.1 如果x是pending态,那么promise必须要在pending,直到 x 变成 fulfilled or rejected.
2.3.2.2 如果 x 被 fulfilled, fulfill promise with the same value.
2.3.2.3 如果 x 被 rejected, reject promise with the same reason.
2.3.3 如果 x 是一个 object 或者 是一个 function
2.3.3.1 let then = x.then.
2.3.3.2 如果 x.then 这步出错,那么 reject promise with e as the reason..
2.3.3.3 如果 then 是一个函数,then.call(x, resolvePromiseFn, rejectPromise)
2.3.3.3.1 resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject);
2.3.3.3.2 rejectPromise 的 入参是 r, reject promise with r.
2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。
2.3.3.3.4 如果调用then抛出异常e
2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略
2.3.3.3.4.3 否则,reject promise with e as the reason
2.3.3.4 如果 then 不是一个function . fulfill promise with x.
2.3.4 如果 x 不是一个 object 或者 function ,fulfill promise with x.
Promise的其他方法
虽然上述的promise源码已经符合PromiseA+的规范,但是原生的Promise还提供了一些其他方法,如:
- Promise.resolve()
- Promise.reject()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.all()
- Promise.race()
下面具体说一下每个方法的实现:
Promise.resolve
Promise.resolve(value) 返回一个以给定值解析后的Promise 对象.
如果 value 是个 thenable 对象,返回的promise会“跟随”这个thenable的对象,采用它的最终状态
如果传入的value本身就是promise对象,那么Promise.resolve将不做任何修改、原封不动地返回这个promise对象。
其他情况,直接返回以该值为成功状态的promise对象。
Promise.resolve = function (param) {
if (param instanceofPromise) {
return param;
}
returnnewPromise((resolve, reject) = > {
if (param &¶m.then&&typeofparam.then === ‘function ‘) {
setTimeout(() = > {
param.then(resolve, reject);
});
} else {
resolve(param);
}
});
}
thenable对象的执行加 setTimeout的原因是根据原生Promise对象执行的结果推断的,如下的测试代码,原生的执行结果为: 20 400 30;为了同样的执行顺序,增加了setTimeout延时。
测试代码:
let p = Promise.resolve(20);
p.then((data) = > {
console.log(data);
});
let p2 = Promise.resolve({
then:function (resolve, reject) {
resolve(30);
}
});
p2.then((data) = > {
console.log(data)
});
let p3 = Promise.resolve(newPromise((resolve, reject) = > {
resolve(400)
}));
p3.then((data) = > {
console.log(data)
});
Promise.reject
Promise.reject方法和Promise.resolve不同,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。
Promise.reject = function (reason) {
returnnewPromise((resolve, reject) = > {
reject(reason);
});
}
Promise.prototype.catch
Promise.prototype.catch 用于指定出错时的回调,是特殊的then方法,catch之后,可以继续 .then
Promise.prototype.catch = function (onRejected) {
returnthis.then(null, onRejected);
}
Promise.prototype.finally
不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.
Promise.prototype.finally = function (callback) {
returnthis.then((value) = > {
returnPromise.resolve(callback()).then(() = > {
return value;
});
}, (err) = > {
returnPromise.resolve(callback()).then(() = > {
throw err;
});
});
}
Promise.all
Promise.all(promises) 返回一个promise对象
如果传入的参数是一个空的可迭代对象,那么此promise对象回调完成(resolve),只有此情况,是同步执行的,其它都是异步返回的。
如果传入的参数不包含任何 promise,则返回一个异步完成.
promises 中所有的promise都promise都“完成”时或参数中不包含 promise 时回调完成。
如果参数中有一个promise失败,那么Promise.all返回的promise对象失败
在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组
Promise.all = function (promises) {
return new Promise((resolve, reject) = > {
let index = 0;
let result = [];
if (promises.length === 0) {
resolve(result);
} else {
function processValue(i, data) {
result[i] = data;
if (++index === promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
//promises[i] 可能是普通值
Promise.resolve(promises[i]).then((data) = > {
processValue(i, data);
}, (err) = > {
reject(err);
return;
});
}
}
});
}
测试代码:
var promise1 = newPromise((resolve, reject) = > {
resolve(3);
})
var promise2 = 42;
var promise3 = newPromise(function (resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function (values) {
console.log(values); //[3, 42, 'foo']
},(err) = >{
console.log(err)
});
var p = Promise.all([]); // will be immediately resolvedvar p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronouslyconsole.log(p);
console.log(p2)
setTimeout(function (){
console.log('the stack is now empty');
console.log(p2);
});
Promise.race
Promise.race函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。
如果传的参数数组是空,则返回的 promise 将永远等待。
如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。
Promise.race = function (promises) {
returnnewPromise((resolve, reject) = > {
if (promises.length === 0) {
return;
} else {
for (let i = 0; i <promises.length; i++) {
Promise.resolve(promises[i]).then((data) = > {
resolve(data);
return;
}, (err) = > {
reject(err);
return;
});
}
}
});
}
测试代码:
Promise.race([
newPromise((resolve, reject) = > { setTimeout(() = > { resolve(100) }, 1000) }),
undefined,
newPromise((resolve, reject) = > { setTimeout(() = > { reject(100) }, 100) })
]).then((data) = > {
console.log('success ', data);
}, (err) = > {
console.log('err ',err);
});
Promise.race([
newPromise((resolve, reject) = > { setTimeout(() = > { resolve(100) }, 1000) }),
newPromise((resolve, reject) = > { setTimeout(() = > { resolve(200) }, 200) }),
newPromise((resolve, reject) = > { setTimeout(() = > { reject(100) }, 100) })
]).then((data) = > {
console.log(data);
}, (err) = > {
console.log(err);
});
- 本文链接:https://xuehuayu.cn/article/49126.html
- 版权声明:① 标为原创的文章为博主原创,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接。② 标为转载的文章来自网络,已标明出处,侵删。