纯手写实现自己的nodejs promise 库
Promise是js推荐的异步原生元素。
回调函数变得越来越少,特别是现在nodejs已经可以用async/await。
async/await也是基于promise的,所以你需要理解primose才能掌握async/await.
本文将会带你编写自己的promise库,以及如何使用async/await.
什么是Promise?
es6标准中,promise是一个构造函数接受运算函数的类。
promise类的实例拥有then方法。
根据标准promise还有其他的熟悉, 单本教程中你可以暂时忽略。
以下是简单promise类的简单形式。
class MyPrimise {
constructor(executor) {}
then(onFullfilled, onRejected) {}
}
运算函数接受两个参数, resolve函数和reject函数。
promise是有3种状态的状态机。
- 等待: 初始状态,意味着promise还未建立
- 完成: 底层操作已经成功,并存在关联值
- 拒绝: 底层操作失败,有关联错误
记住这些,实现第一版的MyPromise构造函数很简单。
constructor(executor){
if(typeof executor !== 'function'){
throw new Error();
}
this.$state = 'PENDING';
this.$chained = [];
const resolve = res => {
if(this.$state !== 'PENDING'){
return;
}
}
}
then函数更简单。记住then函数接受两个参数, onFullfilled 和 onRejected。
then函数负责确保promise完成时调用onFullfilled, promise失败时调用onRejected.
如果Promise已经解决或拒绝, then 应该立即调用onFullfilled 或onRejected。
如果promise仍在等待,then应该将参数推入$chained数组供resolve和reject函数调用。
then(onFulfilled, onRejected) {
if (this.$state === 'FULFILLED') {
onFulfilled(this.$internalValue);
} else if (this.$state === 'REJECTED') {
onRejected(this.$internalValue);
} else {
this.$chained.push({ onFulfilled, onRejected });
}
}
- 另外,es特性中表示调用已经解决或拒绝的promise上的then方法,意味着 onFullfilled 或者 onRejecte函数应该在下次调用。
由于本文的代码会实现简版而不是标准的完整实现,本实现将会忽略该细节。
promise 链
以上实例特别湖绿了最复杂也是最有用的部分: 链式调用。
链式调用的实现是如果onFullfilled或onRejected函数返回promise, then方法应该返回一个被锁住的新promise以匹配返回promise的状态。例如:
p = new MyPromise(resolve => {
setTimeout(() => resolve('World'), 100);
});
p.
then(res => new Promise(resolve => resolve(`Hello, ${res}`))).
then(res => console.log(res));
以下是返回新promise的新then函数,用于链式调用。
then(onFullfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const _onFullfilled = res => {
try {
resolve(onFullfilled(res));
} catch(ex){
reject(ex);
}
};
const _onRejected = err => {
try{
reject(onRejected(err));
} catch(ex){
reject(ex);
}
};
if(this.$state === 'FULLFILLED'){
_onFullfilled(this.$internalValue);
} else if( this.$state === 'REJECTED'){
_onRejected(this.$internalValue);
} else {
this.$chained.push({
onFullfilled: _onFullfilled,
onRejected: _onRejected
});
}
});
}
现在then返回一个新promise, 但是还需要完善。
如果onFullfilled返回promise, resolve需要能够处理promise.
为了支持这,rsolve函数需要使用then方法进行两步递归调用。
以下是扩展的resolve函数.
const resove = res => {
if(this.$state !== 'PENDING'){
return;
}
if(res != null && typeof res.then === 'function'){
return res.then(resolve, reject);
}
this.$state = 'FULLFILLED';
this.$internalValue = res;
for(const {onFullfilled} of this.$chained) {
onFullfilled(res);
}
return res;
}
为了保持简洁,以上实例实现了关键部分,只要promise被锁住以匹配另外的promise, 调用resolve或reject就是空调用。
以上实例中,你可以对等待promise调用resolve,抛错, 之后res.then(resolve, reject)也会是空函数。 这只是个例子,没有完全实现ES6 promise标准。
以上代码显示已解决的promise和已完成的promise之间的区别。
差异很小,主要和链式调用有关。
已解决其实并不是真正的promise状态,但它是ES6特性中定义的术语。
当你resolve以上实现,下面之一会发生:
-
如果你调用resolve(v), v不是promise, 那么promise会立刻变成已完成状态。这种情况下,已解决和已完成是一样的。
-
如果你调用resolve(v), v是另一个promise, 这个promise仍然是等待状态,直到v resolve或rejec。 这种情况下,promise已解决,但仍然是等待状态。
Async/Await
记住await关键字暂停异步函数执行,直到被延迟的promise执行完成。
现在你已经实现了家用promise库,我们看下和async/await一起使用会发生什么。
在then函数中加入console.log语句。
then(onFullfilled, onRejected) {
console.log('Then'), onFullfilled, onRejected, new Error().stack);
return new MyPromise((resolve, reject) => {
});
}
现在,让我们等待MyPromise的实例,看看会发生什么。
run().catch(error => console.error(error.stack));
await function run(){
const start = Date.now();
await new MyPromise(resolve => setTimeout(() => resolve(), 100));
console.log('Elapsed Time', Date.now() - start);
}
记录上面catch函数调用。
catch函数是ES6 promise特性的核心部分。
本文不会详细介绍,因为 catch(f) 等同于 .then(null, f), 没有太多不同。
以下是输出。注意await 隐式调用 then方法, onFullfilled和OnRejected方法会深入到V8引擎的内部中。
另外, await 在下次调用then方法前总是等待。
Then function () { [native code] } function () { [native code] } Error
at MyPromise.then (/home/val/test/promise.js:63:50)
at process._tickCallback (internal/process/next_tick.js:188:7)
at Function.Module.runMain (module.js:686:11)
at startup (bootstrap_node.js:187:16)
at bootstrap_node.js:608:3
Elapsed time 102
后续
Async/await很强大,只有理解promise基础,才能掌握。
Promise也有很多缺点,如: 异步函数执行的同步错误无法获取,
promise结束后内部状态无法改变,这也使得async/await成为可能。
一旦你完全理解了promise, async\await会变得更加容易。
译者注
-
因译者水平有限,如有错误,欢迎指正交流
网友评论