面试官:Promise你倒会用,那你会手写Promise么

1,137 阅读7分钟

在日常开发中,我们常常会使用到Promise,而熟悉了解Promise原理的同学并不多,在面试中也可能被问到与Promise相关的面试题。今天我将带着大家一起实现一个简易版的Promise

实现功能

在实现Promise之前,我们需要知道Promise有哪些功能,这里简单说一下

  • Promise是一个类,它的构造方法需要传入一个函数,并接受两个参数,分别是resolvereject方法,用于改变当前Promise对象的状态。
  • Promise有三种状态,分别是pendingfulfilledrejected
  • then方法用于接收Promise 的成功和失败情况的回调函数,返回一个新的Promise对象。
  • catch方法用于接收Promise失败时的回调函数,与then中的第二个参数相同
  • finally方法在Promise结束时执行指定的回调函数(无论状态是fulfilled或者是rejected

实现Promise

Promise是一个需要被new出来的对象,下面我们使用class关键字创建它。

当然使用function的写法也是可以,但使用class的写法会更加清晰易懂。

class MyPromise{}

Promise在实例化时需要传入一个函数,在构造函数中接收它,并执行。

class MyPromise {
  constructor(executor) {
    executor();
  }
}

const p = new MyPromise(() => {
  console.log("函数被执行了");
});

// 函数被执行了

在构造函数中新增resolvereason方法,用于实例化时接收的两个函数。并将这两个方法传入到executor方法中,它们用于改变当前Promise对象的状态。

class MyPromise {
  constructor(executor) {
    const resolve = (value) => {
      console.log("resolve");
    };
    const reject = (reason) => {
      console.log("reject");
    };
    executor(resolve, reject);
  }
}

const p = new MyPromise((resolve) => {
  resolve(); 
});

// resolve

在类的外部定义Promise对象的三种状态的值,在construct中初始化当前Promise对象的状态为PROMISE_STATUS_PENDING

const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";

// class MyPromise
constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
  }

处理在外部调用resolvereject方法后修改当前Promise的状态并将它们接收的值用两个变量存取下来。

// constructor function
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
  if (this.status === PROMISE_STATUS_PENDING) {
    this.status = PROMISE_STATUS_FULFILLED;
    this.value = value;
  }
};
const reject = (reason) => {
  if (this.status === PROMISE_STATUS_PENDING) {
    this.status = PROMISE_STATUS_REJECTED;
    this.reason = reason;
  }
};

实现then方法

MyPromise类中新建一个then方法,用于接收onFulfilledonRejected方法。在构造函数中创建onFulfilledFnsonRejectedFns两个数组,用于存放then传递过来的回调函数。(Promise实例化出来的对象是可以多次被then调用的,此时应该创建两个数组来存放对应的方法)。

// then
then(onFulfilled, onRejected) {
  onRejected =
    onRejected ||
    ((err) => {
      throw err;
    });
  return new MyPromise((resolve, reject) => {
    // 如果在then调用的时候,状态已经确定下来
    if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
      try {
        const value = onFulfilled(this.value);
        resolve(value);
      } catch (error) {
        reject(error);
      }
    }
    if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
      try {
        const reason = onRejected(this.reason);
        resolve(reason);
      } catch (error) {
        reject(error);
      }
    }
    if ((this.status = PROMISE_STATUS_PENDING)) {
      if (typeof onFulfilled === "function") {
        this.onFulfilledFns.push(() => {
          try {
            const value = onFulfilled(this.value);
            resolve(value);
          } catch (error) {
            reject(error);
          }
        });
      }
      if (typeof onRejected === "function") {
        this.onRejectedFns.push(() => {
          try {
            const reason = onRejected(this.reason);
            resolve(reason);
          } catch (error) {
            reject(error);
          }
        });
      }
    }
  });
}

在构造函数中的resolvereject的函数内修改当前Promise对象的状态,并执行then方法中存储的方法。

const resolve = (value) => {
  if (this.status === PROMISE_STATUS_PENDING) {
    queueMicrotask(() => {
      if (this.status !== PROMISE_STATUS_PENDING) return;
      this.status = PROMISE_STATUS_FULFILLED;
      this.value = value;
      this.onFulfilledFns.forEach((fn) => fn(this.value));
    });
  }
};
const reject = (reason) => {
  if (this.status === PROMISE_STATUS_PENDING) {
    queueMicrotask(() => {
      if (this.status !== PROMISE_STATUS_PENDING) return;
      this.status = PROMISE_STATUS_REJECTED;
      this.reason = reason;
      this.onRejectedFns.forEach((fn) => fn(this.reason));
    });
  }
};
测试then方法
const p = new MyPromise((resolve) => {
  resolve(123); //
});

p.then((res) => {
  console.log("res---", res);
  return "_island";
}).then((res) => {
  console.log("res---", res);
});

// res: 123
// res--- _island

实现catch方法

catch方法相当于then方法中的第二个回调函数,也就是这样子的一个写法then(res=>res).catch(e=>e)。在MyPromise中新增catch方法,接收一个回调函数。并将这个回调函数传到then方法中的第二个参数。

catch(onRejected){
  return this.then(undefined,onRejected)
}

then方法我们也需要做出判断,当then方法没有传递第二个参数时,默认设定一个处理函数。

then(onFulfilled, onRejected) {
  onRejected =onRejected || (err=>{ throw err })
  // ...
}
测试catch方法
const p = new MyPromise((resolve,reject) => {
  reject('失败了'); //
});

p.then((res) => {
  console.log("res---", res);
  return "_island";
}).then(res=>{
  console.log('res---',res);
}).catch((err)=>{
  console.log('catch',err);
})

// catch 失败了

实现finally方法

finally实现起来也是很简单的,当Promise对象的状态发生改变之后执行指定的回调函数。在MyPromise中新增finally方法,接收一个回调函数。并将这个函数传递到then方法。

finally(onFinally) {
  this.then(
    () => {
      onFinally();
    },
    () => {
      onFinally();
    }
  );
}

值得注意的是在then方法中我们也要判断onFulfilled参数是否有值,如果没有赋予它一个默认值。

then(onFulfilled, onRejected) {
  onFulfilled = onFulfilled || (value => value);
  // ...
}
测试finally方法
p.then((res) => {
  console.log("res---", res);
}).finally(() => {
  console.log("finally");
});

// res--- result
// finally

补充

实现resolve reject 方法

resolvereject方法都是用于将一个普通对象转为一个Promise对象,只是返回出来是状态不相同。它们都是静态方法。

其实这两个方法实现起来很简单,我们在MyPromise类中新增这两个静态方法,并调用对应的回调函数就可以了。

static resolve(value) {
  return new MyPromise((resolve) => resolve(value));
}
static reject(reason) {
  return new MyPromise((undefined, reject) => reject(reason));
}
测试resolve reject 方法
MyPromise.resolve({ name: "_island" }).then((res) => console.log(res));
// { name: '_island' }

MyPromise.reject({ error: "msg" }).catch((err) => console.log(err));
// { error: 'msg' }

实现all方法

all方法接收一组Promise对象当状态全为fulfilled时再将这些Promise的结果放到一个数组中并返回成一个新的成功Promise对象,如果其中有一个Promise对象状态为rejected时,则返回一个新的失败Promise对象

MyPromise类中新增这all静态方法,接收一组Promise对象,通过遍历的方式将这些promise返回的结果集放入到一个数组内,最后通过resolve将这个数组返回即可。(需要注意的是在每次将数据放入数组之后我们需要判断当前数组中的数量和传入的数组中的数组是否一致,如果一致则调用resolve

static all(promises) {
  return new MyPromise((resolve, reject) => {
    const results = [];
    promises.forEach((promise) => {
      MyPromise.then(
        (res) => {
          results.push(res);
          if (results.length === promises.length) {
            resolve(results);
          }
        },
        (err) => {
          reject(err);
        }
      );
    });
  });
}
测试all方法
// const p1=new MyPromise((resolve)=>setTimeout(()=>{resolve('p1')},2000))
// const p2=new MyPromise((resolve)=>resolve('p2'))
// const p3=new MyPromise((resolve)=>resolve('p3'))
// MyPromise.all([p1,p2,p3]).then(res=>console.log(res)).catch(err=>console.log('err'))
// [ 'p2', 'p3', 'p1' ]

实现allSettled方法

allSettled方法的和all方法类似,也是接收一组Promise对象,不同的是这一组Promises对象中无论是成功还是失败都会被存放到一个数组中,最后并返回一个新的成功Promise对象。

创建一个存储结果的数组,使用foreach遍历接收到的数组中每一个Promise对象,并通过返回的状态放入到一个对象中(在对象中,如果Promise状态为fulfilled,那健为vlaue里面存放对应的内容,如果是rejected,健则是reason,存放对应的内容),最终返回这个数组。

static allSettled(promises) {
  return new MyPromise((resolve) => {
    const results = [];
    promises.forEach((promise) => {
      MyPromise.then(
        (res) => {
          results.push({
            status:PROMISE_STATUS_FULFILLED,
            value: res
          });
          if (results.length === promises.length) {
            resolve(results);
          }
        },
        (err) => {
          results.push({
            status: PROMISE_STATUS_REJECTED,
            reason: resolve(res)
          });
          if (results.length === promises.length) {
            resolve(results);
          }
        }
      );
    });
  });
测试allSettled
const p1 = new MyPromise((resolve) =>
  setTimeout(() => {
    resolve("p1");
  }, 2000)
);
const p2 = new MyPromise((resolve) => resolve("p2"));
const p3 = new MyPromise((resolve) => resolve("p3"));
MyPromise.allSettled([p1, p2, p3]).then((res) => console.log(res));
// [
//   { status: 'fulfilled', value: 'p2' },
//   { status: 'fulfilled', value: 'p3' },
//   { status: 'fulfilled', value: 'p1' } 
// ]

实现race方法

race方法接收一组Promises对象,顾名思义,比的是速度,这一组Promise对象中当前其中一个对Promise对象被fulfilled或者rejected时,返回一个新的Promise状态为fulfilled或者rejected

这里使用foreach遍历直接调用MyPromise.then方法,将对应的方法传入即可。

static race(promises) {
  return new MyPromise((resolve, reject) => {
    promises.forEach((promise) => {
      MyPromise.then(resolve, reject);
    });
  });
}
测试race方法
const p1=new MyPromise((resolve)=>setTimeout(()=>{resolve('p1')},2000))
const p2=new MyPromise((resolve)=>setTimeout(()=>{resolve('p2')},3000))
const p3=new MyPromise((resolve)=>resolve('p3'))
MyPromise.race([p1,p2,p3]).then(res=>console.log(res))

实现any方法

any方法接收一组Promise对象,如果其中有一个Promise状态为fulfilled时则返回一个成功的Promise对象,如果全部Promise对象都失败了则抛出AggregateError的错误。

使用foreach方法实现,将数组中每一个Promise对象遍历出来,resolve方法可以直接传入,rejected方法我们需要处理一下,判断方式和上面的all方法一样,当全部Promise对象的状态为rejected时,返回AggregateError类型的错误。

static any(promises) {
  const errors = [];
  return new MyPromise((resolve, reject) => {
    promises.forEach(
      (promise) => {
        MyPromise.then(resolve, (err) => {
          errors.push(err);
          if (errors.length === promises.length) {
            reject(new AggregateError(errors));
          }
        });
      }  
    );
  });
}
测试any方法
const p1 = new MyPromise((resolve, reject) =>
  setTimeout(() => {
    reject("p1");
  }, 2000)
);
const p2 = new MyPromise((resolve, reject) => reject("p2"));
const p3 = new MyPromise((resolve, reject) => reject("p3"));
MyPromise.any([p1, p2, p3])
  .then((res) => console.log(res))
  .catch((err) => console.log("err:", err));

总结

这个简易版的Promise实现起来代码量也不是很多,不到100行。但是理解起来还是有一点点难度的。then方法是这整个简易版Promise的实现起来复杂的方法,涉及到各种方法的判断,还有调用的顺序。其实只要捋清楚执行的顺序还是蛮好理解的。