JS-JS模拟实现Promise-源码解析
Posted 小飞2020
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS-JS模拟实现Promise-源码解析相关的知识,希望对你有一定的参考价值。
1. 前言
Promises/A+规范:
https://www.ituring.com.cn/article/66566(中文);https://promisesaplus.com/(英文);
ES6 中的promise
采用 Promise/A+ 规范,链接如上。
源码来源:https://repl.it/@morrain2016/Promise
本文实质上是对这份源码的一次解析。
这份源码是用JS代码对promise
的模拟实现,与原生的promise
势必存在些许的差异,但是,理解了这份源码,必定会帮助你更深入地理解promise
底层原理。
2. 源码解析
本章节主要分成两个小节对源码进行解析:
- 尽可能地将源码缩减至最小,但保证了其核心功能的实现。通过这一阶段,你就深入理解了
promise
的核心原理; - 完整源码的其它部分详解。
2.1 核心代码
本小节分为两个部分,第一个部分为没有注释的核心代码,第二部分为详细注释的核心代码,目的很简单:读者先自己思考,若在某处卡住了可前往第二部分找到答案。
目标是:实现一个myPromise
类,能成功执行下面的代码:
new myPromise(res => {
setTimeout(() => {
console.log("test1"); // 两秒后输出 test1
res("data1");
}, 2000);
}).then((data) => {
console.log(data); // 两秒后紧接着test1输出 data1
return new myPromise(res => {
setTimeout(() => {
console.log("test2"); // 三秒后输出 test2
res("data2");
}, 1000);
})
}).then(data => {
console.log(data); // 三秒后紧接着test2输出 data2
})
2.1.1 challenge
本小节对源码进行了尽可能的缩减,仅保留了其核心功能,建议读者复制代码直接进行阅读。本部分源码无任何注释,因此为challenge部分,若读者在阅读过程中遇到了困难,可前往 2.1.2 help 部分(源码+详细注释)寻求帮助。
备注:本小节的代码只保留了promise
的两种状态:pending
和 fulfilled
,对外只暴露出 then
方法。rejected
状态和其它方法将在第二阶段解析。
class myPromise {
callbacks = [];
state = \'pending\';
value = null;
constructor(fn) {
fn(this._resolve.bind(this));
}
then(onFulfilled) {
return new myPromise((resolve) => {
this._handle({
onFulfilled: onFulfilled || null,
resolve: resolve,
});
});
}
_handle(callback) {
if (this.state === \'pending\') {
this.callbacks.push(callback);
return;
}
if (!callback.onFulfilled) {
callback.resolve(this.value);
return;
}
var ret = callback.onFulfilled(this.value);
callback.resolve(ret);
}
_resolve(value) {
if (this.state !== \'pending\') return
if (value && (typeof value === \'object\' && value !== null || typeof value === \'function\')) {
var then = value.then;
if (typeof then === \'function\') {
then.call(value, this._resolve.bind(this));
return;
}
}
this.state = \'fulfilled\';
this.value = value;
this.callbacks.forEach(callback => this._handle(callback));
}
}
2.1.2 help
详细注释版:
class myPromise {
callbacks = [];// resolve执行后,会依次执行该数组中的回调函数
state = \'pending\';//记录promise的当前状态: "pending"/"fulfilled"/"rejected"
value = null;//记录resolve(data)中传递的数据data
constructor(fn) {
// 立即执行传入promise中的函数fn
// 当fn函数中执行resolve(),其实是在调用内部方法:this._resolve.bind(this)
fn(this._resolve.bind(this));
}
// promise实例的then方法, 将传进来的函数 onFulfilled 放入callback中
then(onFulfilled) {
// then方法返回一个新的promise实例, 以实现链式调用: promise.then().then()....
return new myPromise((resolve) => {
// 这个新promise实例的状态由resolve控制, 所以将resolve一并打包, 后面会用到
this._handle({
onFulfilled: onFulfilled || null,
resolve: resolve,
});
});
}
_handle(callback) {
// 顺着then方法的逻辑, 将callback对象放入存储数组中
if (this.state === \'pending\') {
this.callbacks.push(callback);
return;
}
// 如果then中没有传递参数, 即形式为 promise.then().then(cb),
// 那么第一个then创造的promise实例, 应该立即执行resolve, 进入fulfilled状态, 并执行cb();
if (!callback.onFulfilled) {
callback.resolve(this.value);//将value传递下去
return;
}
/**
* promise.then(()=>{
* return 666;
* }).then(data =>{
* console.log(data); // 打印出666
* })
* 这里相当于在执行第一个then中的回调函数, 并将返回的 "666" 传递给下一个then;
*
* 思考: 如果返回的不是 "666", 而是一个promise呢? 可以去看_resolve方法中标记为 **(1)** 的代码逻辑
*/
var ret = callback.onFulfilled(this.value);
callback.resolve(ret);
}
_resolve(value) {
// 这行代码的效果就是: new myPromise(res=>{res(); res(); res();}), 第一个res()后面的所有res()都会忽略
if (this.state !== \'pending\') return
// **(1)** 这一块的逻辑基于value是一个promise实例. 这一块建议最后研究
if (value && (typeof value === \'object\' && value !== null || typeof value === \'function\')) {
var then = value.then;// promise拥有then方法, 据此来判断该对象是不是一个promise
if (typeof then === \'function\') {
// value是一个promise实例, 此时当前promise的状态应当依赖于 value这个promise实例的状态
then.call(value, this._resolve.bind(this));
return;
// 看个例子
/**
* promise.then(()={
* return new myPromise(res => {
* res(data);
* })
* })
* 这里回调函数中返回的是一个promise实例, 相当于上面的value
* then.call(value, this._resolve.bind(this)), 即value.then(this._resolve.bind(this)),
* 当value这个promise实行执行fulfilled后, 就会执行 this._resolve.bind(this), 也就是外层promise的 resolve(data)
*/
}
}
// 下面的逻辑就比较简单了
this.state = \'fulfilled\';//改变状态
this.value = value;//保存结果
this.callbacks.forEach(callback => this._handle(callback));//依次调用callbacks数组中的所有函数
}
}
2.2 其它部分详解
如果读者已通过第一阶段,建议访问该地址https://www.ituring.com.cn/article/66566,直接复制完整源码进行阅读,若途中遇到问题,可参考本小节的源码注释。
本小节主要从以下几个部分进行解析:
rejected
状态的补充;- 类的静态方法:resolve、reject、all、race ;
- 实例的
catch
和finally
方法。
2.2.1 rejected
状态的补充
class myPromise {
callbacks = [];
state = \'pending\';
value = null;
constructor(fn) {
// 这里的参数多了一个_reject函数
fn(this._resolve.bind(this), this._reject.bind(this));
}
then(onFulfilled, onRejected) {
return new myPromise((resolve, reject) => {
// 这里增加了一部分代码
this._handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
}
_handle(callback) {
if (this.state === \'pending\') {
this.callbacks.push(callback);
return;
}
// 这里和上个版本的逻辑类似, 只不过增加了 rejected 状态
let cb = this.state === \'fulfilled\' ? callback.onFulfilled : callback.onRejected;
if (!cb) {
cb = this.state === \'fulfilled\' ? callback.resolve : callback.reject;
cb(this.value);
return;
}
/**
* cb函数在执行过程中可能会遇到报错, 如
* promise.then(()=>{
* throw Error("error!")
* })
* 这时应当执行reject, 并将error信息传递出去
*/
let ret;
try {
ret = cb(this.value);
cb = this.state === \'fulfilled\' ? callback.resolve : callback.reject;
} catch (error) {
ret = error;
cb = callback.reject
} finally {
cb(ret);
}
}
_resolve(value) {
if (this.state !== \'pending\') return
if (value && (typeof value === \'object\' || typeof value === \'function\')) {
var then = value.then;
if (typeof then === \'function\') {
then.call(value, this._resolve.bind(this), this._reject.bind(this));
return;
}
}
this.state = \'fulfilled\';//改变状态
this.value = value;//保存结果
this.callbacks.forEach(callback => this._handle(callback));
}
// 增加了_reject内部方法
_reject(error) {
if (this.state !== \'pending\') return
/**
* 这里没有对error是不是promise的判断, 因此当onRejected函数返回一个promise实例时,会直接将这个实例传递出去,如
* new myPromise((res, rej) => {
// 将一个promise实例传入rej()函数中
rej(new myPromise(() => {console.log("test")}));
}).then(() => { }, data => {
console.log(data); // 这里将打印出上面的promise实例
})
*/
this.state = \'rejected\';
this.value = error;
this.callbacks.forEach(callback => this._handle(callback));
}
}
2.2.2 类的静态方法(resolve、reject、all、race )
- Promise.resolve()、Promise.reject():
// Promise.resolve()返回一个promise实例, 下面的代码都围绕着这一点, 只是对传进来的不同参数响应不同的行为
static resolve(value) {
if (value && value instanceof Promise) {
// value是promise实例, 直接返回该实例
return value;
} else if (value && typeof value === \'object\' && typeof value.then === \'function\') {
// value不是promise实例, 但是具有then方法; 即它是一个 thenable 对象
let then = value.then;
return new Promise(resolve => {
then(resolve);
});
} else if (value) {
// value 不是promise实例也不是thenable对象
return new Promise(resolve => resolve(value));
} else {
// value不存在
return new Promise(resolve => resolve());
}
}
static reject(value) {
if (value && typeof value === \'object\' && typeof value.then === \'function\') {
let then = value.then;
return new Promise((resolve, reject) => {
then(reject);
});
} else {
return new Promise((resolve, reject) => reject(value));
}
}
Promise.reject 与 Promise.resolve 类似,区别在于 Promise.reject 始终返回一个状态的 rejected 的 Promise 实例,而 Promise.resolve 的参数如果是一个 Promise实例的话,返回的是参数对应的 Promise 实例,所以状态不一定。
- Promise.all()、Promise.race()
static all(promises) {
// 返回一个promise实例
return new Promise((resolve, reject) => {
let fulfilledCount = 0 // 计数
const itemNum = promises.length // promises中的promise个数
const rets = Array.from({ length: itemNum }) // 用来保存每个promise的返回结果
promises.forEach((promise, index) => {
Promise.resolve(promise).then(result => {
fulfilledCount++;
rets[index] = result;
if (fulfilledCount === itemNum) {
resolve(rets);
}
}, reason => reject(reason));
})
})
}
static race(promises) {
return new Promise(function (resolve, reject) {
// 依次执行promises中的各个promise,只要有一个promise状态确定了, 就执行resolve或者reject
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(function (value) {
return resolve(value)
}, function (reason) {
return reject(reason)
})
}
})
}
2.2.3 实例方法(catch、finally)
// promise.catch(cb)相当于 promise.then(null, cb)
catch(onError) {
return this.then(null, onError);
}
finally(onDone) {
if (typeof onDone !== \'function\') return this.then();
let Promise = this.constructor;
return this.then(
value => Promise.resolve(onDone()).then(() => value),
reason => Promise.resolve(onDone()).then(() => { throw reason })
);
// 如果这里写成: return this.then(onDone, onDone), 会导致这样的问题:
// 1.onDone会接收上一个promise传过来的参数; 2.如果onDone返回一个promise, 它将影响最终的状态;
}
3. 源码的缺陷
这份源码存在一些问题,比如在执行下面这段代码时:原生promise
的输出结果是: 2, 1,因为then
方法中的代码是异步执行的;而这份源码实现的promise的输出结果是:1,2,这主要是因为当前源码的then
方法是同步执行。
new Promise((res) => {
res();
}).then(() => {
console.log(1);
})
console.log(2);
4. 参考资料
图解 Promise 实现原理:https://zhuanlan.zhihu.com/p/58428287
【翻译】Promises/A+规范:https://www.ituring.com.cn/article/66566
源码:https://repl.it/@morrain2016/Promise
以上是关于JS-JS模拟实现Promise-源码解析的主要内容,如果未能解决你的问题,请参考以下文章
VSCode自定义代码片段12——JavaScript的Promise对象