这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战
前言
本文为手写一个基础版 promise ,用于理解其常用方法。涉及的方法有 then、all、race、resolve、reject、finally、catch,其中 all、race、resolve、reject为静态方法即在方法名前面添加 static 关键字(在本文中先实现then、all)
创建 promise
使用 class 关键字来声名类,设置两个属性 promiseState、promiseResult,初始值分别为 pending 和 undefined;在构造函数中接收一个函数类型参数,该参数函数也可以接收两个参数 resolve 和 reject,分别用于成功的回调和失败的回调。
// 设置全局状态值,便于管理
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECT = 'reject';
class MyPromise {
PromiseState = PENDING;
PromiseResult = undefined;
constructor (exectutor) {
try {
exectutor(this.resolve.bind(this), this.reject.bind(this));
} catch (err) {
this.reject(err);
}
}
resolve (val) {
if (this.PromiseState !== PENDING) return; // 状态需要变更时,当前状态必须为pending
this.PromiseState = FULFILLED;
this.PromiseResult = val;
}
reject (reason) {
if (this.PromiseState !== PENDING) return;
this.PromiseState = REJECT;
this.PromiseResult = reason;
}
}
复制代码
then 方法
定义</br>
1,接受两个回调函数</br>
2,如果状态变为成功状态,则执行第一个回调</br>
3,如果状态变为失败状态,则执行第二个回调</br>
4,如果状态变为待定状态,则保留当前的两个回调</br>
then 会返回一个新的 Promise 对象,该对象的状态和结果由回调函数的返回值决定</br>
如果返回值是 promise 对象,</br>
返回值为成功,新promise 就是成功</br>
返回值为失败,新promise 就是失败</br>
如果返回值非promise对象</br>
新promise就是成功,它的值就是返回值</br>
特别是,如果第二个回调不为函数时,新 promise 一定是失败的状态,值为错误信息</br>
复制代码
首先对传入的回调函数经行类型检查,判断它们是否为函数。这里使用 Object.prototype.toString.call
可以检测到箭头函数,如果是 typeof 的话就检测不出箭头函数。对于第一个回调,如果不是函数的话,可以直接提供一个将入参直接返回的箭头函数;对于第二个回调,如果不是函数的话,要让返回的 promise 为失败状态,直接抛出异常即可,异常值则为传入的值。然后由于要返回一个 promise 对象,定义一个新的 promise 对象用于返回,由于在每个条件下具体代码的逻辑差不多,重复率较高,所以提一个公共方法出来;在这个公共方法中接收同状态下执行的不同回调,具体针对新 promise 状态进行逻辑处理。
以成功状态时调用第一个回调为例,可以先对回调的结果做判断:定义一个变量接收回调的返回值,若返回值是一个 promise 对象,则通过它的 then 方法来间接执行新 promise 的 resolve 或 reject 以改变新 promise 状态;若返回不是 promise 对象,则新 promise 的状态为成功,故直接执行其(新 promise) resolve方法。由于 then 方式是链式调用,故不应该出现 then 前面的 promise 与后面的 promise 完全相同的情况,因而添加一个条件判断 if (x === thenPromise)
,在这个条件判断中,由于要在 thenPromise 中使用它自己,那么就要保证这个时候 thenPromise 是有值的,就通过 queueMicrotask
方法实现一个异步操作;再者使用 try catch 来进行错误处理,有错误就直接调用新 promise 的 reject。
错误状态时调用第二个回调与其逻辑基本相同。待定状态时要保留传入的两个回调,在合适的时候触发,故设置两个对应的数组属性(fulfilledCbs、rejectCbs)用来储存这两个回调,合适的时候就是执行 resolve 或 reject 时,此刻依次调用数组项方法即可。
then (onFulfilled, onRejected) {
// 参数判断
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
const thenPromise = new MyPromise((resolve, reject) => {
const resolvePromise = cb => {
queueMicrotask(() => { // 异步方法,让 thenPromise 先进行赋值操作
try {
let x = cb(this.PromiseResult);
if (x === thenPromise) {
throw new Error('then lian 错误');
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
} catch (err) {
console.error(err);
reject(err);
}
});
};
if (this.PromisState === FULFILLED) {
resolvePromise(onFulfilled);
} else if (this.PromisState === REJECT) {
resolvePromise(onRejected);
} else {
this.fulfilledCbs.push(resolvePromise.bind(this, onFulfilled));
this.rejectCbs.push(resolvePromise.bind(this, onFulfilled));
}
});
return thenPromise;
}
// class MyPromise
fulfilledCbs = [];
rejectCbs = [];
// resolve 方法
while (this.fulfilledCbs.length) {
this.fulfilledCbs.shift()(this.PromiseResult);
}
// reject 方法
while (this.rejectCbs.length) {
this.rejectCbs.shift()(this.PromiseResult);
}
复制代码
all 方法
是一个静态方法,需要一个数组作为参数,返回一个 Promise</br>
参数数组中,如果所有 promise 对象都为成功,则返回成功状态的 promise对象</br>
参数数组中,如果有一个 promise 对象为失败,则返回失败状态的 promise对象,且中止执行后续 promise 对象</br>
复制代码
定义一个用于收集成功 promise 结果的数组 result,然后参数数组通过 foreach 来处理每一项。首先判断当前项是否为一个 promise ,是的话就通过其 then 方法来确定其状态,如果是成功的回调就将结果存入 result,如果是失败的回调则直接调用新 promise 的 reject 设置状态;如果当前项不是 promise 则直接将其加入到 result 数组中。最后判断 result 长度是否和参数数组相同,相同就调用新 promise 的 resolve 设置状态。
static all (arr) {
let result = [];
const allPromise = new MyPromise((resolve, reject) => {
arr.forEach(item => {
if (item instanceof MyPromise) {
item.then(val =>{
addData(val);
}, reject);
} else addData(item);
});
function addData (val) {
result.push(val);
if (result.length === arr.length) resolve(result);
}
});
return allPromise;
}
复制代码
参考
# 「景水」- 手写 Promise 心得分享
本文就先到此打住,剩下的几个方法在明天的文章中将进行讲述。源码在这里