手写简易版Promise-麻雀虽小五脏俱全
要实现一个PromiseA+规范的代码量是很大的,而且实现的过程中有大量的边界条件是不容易理解的。在一场面试中很难会要求面试者手写一个符合A+规范的Promise,但是让大家简单实现一下还是可能出现的。
下面我和大家一起实现一个简单的Promise,但是他也是按照A+规范的思路来的,只不过省略了一些特殊的处理。 我们要达到的目的就是可以完成异步的功能,支持简单的链式调用。
- 使用class的形式实现Promise,并声明需要使用的变量和方法
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(fn) {
this.states = PENDING;
this.value = null;
this.reason = null;
}
resolve(value) {
}
reject(reason) {
}
then(onFulfilled,onRejected) {
}
}
- 根据promise的状态流转机制实现对应的状态变化
class MyPromise {
constructor(fn) {
this.states = PENDING;
this.value = null;
this.reason = null;
}
resolve(value) {
if(this.states === PENDING) { //new
this.value = value;
this.states = FULFILLED;
}
}
reject(reason) {
if(this.states === PENDING) { //new
this.reason = reason;
this.states = REJECTED;
}
}
}
- Promise构建时传入一个函数fn,他接受两个参数resovle和reject并立即执行,执行过程中出错调用reject
class MyPromise {
constructor(fn) {
this.states = PENDING;
this.value = null;
this.reason = null;
try {
fn(this.resolve.bind(this),this.reject.bind(this));
} catch (err) {
this.reject(error);
}
}
}
- then方法的实现,他接受onFulfilled,onRejected可选参数,并在promise状态变化时异步调用相应任务
class MyPromise {
constructor(fn) {
this.states = PENDING;
this.value = null;
this.reason = null;
this.fulfilledCallbacks = [];//new 存放pending状态下获得的then方法回调onFulfilled
this.rejectedCallbacks = [];//new 存放pending状态下获得的then方法回调onRejected
}
then(onFulfilled,onRejected) {
//可选参数,处理传入不是函数情况,或者不传入
const fulfilledFn = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
const rejectedFn = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason};
//then方法返回一个新的Promise
//回调方法报错需要reject,调用需要异步,使用queueMicrotask模拟微任务调用
switch (this.states) {
case PENDING: {
const newPromise = new MyPromise((resolve,reject) => {
//状态pending暂时不执行注册到队列
this.fulfilledCallbacks.push(
() => {
queueMicrotask(() => { //异步调用的回调
try { //被trycatch包裹检测报错
let x = fulfilledFn(this.value); //执行回调
resolve(x);
} catch(rea) {
reject(rea);
}
})
}
)
this.rejectedCallbacks.push(
() => {
queueMicrotask(() => { //异步调用的回调
try { //被trycatch包裹检测报错
let x = rejectedFn(this.value); //执行回调
resolve(x);
} catch(rea) {
reject(rea);
}
})
}
)
})
return newPromise;
}
case FULFILLED: {
const newPromise = new MyPromise((resolve,reject) => {
queueMicrotask(() => { //异步调用的回调
try { //被trycatch包裹检测报错
let x = fulfilledFn(this.value); //执行回调
resolve(x);
} catch(rea) {
reject(rea);
}
})
})
return newPromise;
}
case REJECTED: {
const newPromise = new MyPromise((resolve,reject) => {
queueMicrotask(() => { //异步调用的回调
try { //被trycatch包裹检测报错
let x = rejectedFn(this.reason); //执行回调
resolve(x);
} catch(rea) {
reject(rea);
}
})
})
return newPromise;
}
}
}
}
- Promise状态改变时,调用提取注册的回调。或者说resolve/reject方法调用时回调then方法注册的函数,可以直接在resolve/reject的代码里面写循环调用注册在callbacks里面的方法,在这里我用一个set/get更语义化的实现一下
class MyPromise {
constructor(fn) {
this._states = PENDING;
this.value = null;
this.reason = null;
}
set states(newStates) {
this._states = newStates;
if(newStates === FULFILLED) {
this.fulfilledCallbacks.forEach(callback => callback(this.value));
}
if(newStates === REJECTED) {
this.rejectedCallbacks.forEach(callback => callback(this.reason));
}
}
get states() {
return this._states;
}
}
- 最复杂的一步,实现对then方法onFulfilled/onRejected回调返回值的处理,其实上面我们已经then方法已经返回了一个新的Promise来支持链式调用,但是规范里面为了处理各种不同情况,专门规定了一个resolvePromise来处理返回值。
我们简单的了解一下resolvePromise吧,参数为newPromise,返回值x,resolve,reject。下面实现一个最简单resolvePromise。
class MyPromise {
resolvePromise(newPro,x,resolve,reject) {
if(newPro === x) {
return reject( new TypeError('xxx'));//返回值和then方法生成的promise相等会产生循环依赖
}
if( x instanceof MyPromise) {
//newPromise状态要和x保持一致
x.then((y) => {
this.resolvePromise(newPro,y,resolve,reject);
},reject)
}
else if(typeof x === 'object' || typeof x === 'function') {
//这里比较复杂主要是处理回调方法返回一个thenable类型怎么办,规范规定的很细致。简版里面我们不做实现了,有需要可以查看promiseA+规范。我们简单的返回resolve
resolve(x);
}
else {
//其他基础类型的值直接resolve
resolve(x);
}
}
}
上面就是以一个A+规范的思路实现的简版promise。
下面我们分别实现一下Promise身上的静态方法,这是面试常考题比较简单。希望大家都能掌握
Promise.resolve / Promise.reject
class MyPromsie {
static resolve(value) {
if(value instanceof MyPromise) {
return value;
}
return new MyPromise((resolve) => {
resolve(value);
});
}
static reject(reason) {
return new MyPromise((resolve,reject) => {
reject(reason);
});
}
}
Promise.race / Promise.any
class MyPromsie {
//返回数组里面第一个成功或者失败
static race(promiseList) {
return new Promise((resolve,reject) => {
const len = promiseList.length;
if(len === 0) {
return resolve();
}
for(let i = 0; i < len; i++) {
MyPromsie.resolve(promiseList[i]).then((value) => {
return resolve(value);
},(reason) => {
return reject(value);
})
}
})
}
//返回第一个成功或者全部失败
static any(promiseList) {
let resList = [];
return new Promise((resolve,reject) => {
const len = promiseList.length;
let num = 0;
if(len === 0) {
return reject(resList);
}
for(let i = 0; i < len; i++) {
MyPromsie.resolve(promiseList[i]).then((value) => {
return resolve(value);
},(reason) => {
resList[i] = reason;
num++;
if(num === len) {
return reject(resList);
}
})
}
})
}
}
Promise.all / Promise.allSettled
class MyPromsie {
//返回数组里面全部成功或者一个失败
static all(promiseList) {
return new Promise((resolve,reject) => {
const len = promiseList.length;
let resList = [];
let num = 0;
if(len === 0) {
return resolve();
}
for(let i = 0; i < len; i++) {
MyPromsie.resolve(promiseList[i]).then((value) => {
resList[i] = value;
num++;
if(num === len) {
return resolve(resList);
}
},(reason) => {
return reject(value);
})
}
})
}
//返回全部成功或者失败
static allSettled(promiseList) {
return new Promise((resolve,reject) => {
let resList = [];
const len = promiseList.length;
let num = 0;
if(len === 0) {
return reject(resList);
}
for(let i = 0; i < len; i++) {
MyPromsie.resolve(promiseList[i]).then((value) => {
resList[i] = value;
num++;
if(num === len) {
return resolve(resList);
}
},(reason) => {
resList[i] = reason;
num++;
if(num === len) {
return resolve(resList);
}
})
}
})
}
}