手写一个Promise

134 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情

前言

关于Promise,想必大家并不陌生,使用起来比较方便,我们也是在各种项目中见到它越来越多的身影。同时,手写promise也成为各种面试官必问的一道考点,它也几乎成了各位前端人必须掌握的技能能之一。网上有很多优秀的手写promise的文章,但是学习这种事嘛,只有自己写下来才是完全掌握,所以,我才写下这篇文章。

Promise优点

Promise之所以能够广泛地被使用,肯定是它有着别人难以替代的魅力,在之后我们可能会这样处理一些异步操作。

ajax.get({ url: 'xxx' }, (data1) => {
  ajax.get({ url: 'xxxx', data: data1 }, (data2) => {
    ajax.get({ url: 'xxx', data: data2 }, (data3) => {
      ...
    });
  });
});

下一个接口的入参依赖上一个接口的返回值,我们只能通过这样不断嵌套的方式,这也就是我们常说的回调地狱的问题。而Promise的出现,它提供链式调用的方式方便我们处理返回结果,我们也可以配合async awit的使用,来更好的处理异步操作。好了,我们废话少说,直接实现。

简单实现

实现标准

我们实现的Promise必须满足PromiseA+规范,标准的Promise也是基于此标准实现。中文翻译

  • promise 的状态

pormise 必须是以下三个状态之一: pending, fulfilled, rejected.

  • then 方法

promise 必须提供一个 then 方法,能由此去访问当前或最终的 value 或者 reason 。

这里我们标注出我们Promise的要求,我们实现的标准都应该在此基础上。

基础实现

// Promise的基础状态
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
  constructor(executor) {
    this.value = undefined; // 成功的值
    this.reason = undefined; // 失败的原因
    this.status = PENDING; // 默认的pending状态

    const resolve = (value) => {
      this.value = value;
      this.status = FULFILLED;
    };
    const reject = (reason) => {
      this.reason = reason;
      this.status = REJECTED;
    };

    try {
      executor(resolve, reject);
    } catch (e) {
      // 如果执行时发生了异常 那么这个异常就是失败的原因
      reject(e);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled(this.value);
    onRejected(this.reason);
  }
}

上面是我们根据基础要求写出的基本代码,它满足了Promise关于PENDING FULFILLED REJECTED三种状态,和一个then方法的特性。但是技术细节并不完善,下面我们一一实现这些技术细节。

状态凝固

const Promise = require('./promise');

const p = new Promise((resolve, reject) => {
  resolve('成功');
  reject('失败');
});

p.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  },
);

当我们使用我们自己的Promise的时候,执行上述代码会发现依次打印成功 失败这是不符合Promise标准的。

Promise拥有三种状态,但是只有在处于PENDING状态时,才可以进行更改,而且在更改之后状态就会凝固不能进行更改,这里需要我们对resolverejectthen方法进行改造。

  const resolve = (value) => {
+   if (this.status === PENDING) {
      this.value = value;
      this.status = FULFILLED;
+   }
  };

  const reject = (reason) => {
+   if (this.status === PENDING) {
      this.reason = reason;
      this.status = REJECTED;
+   }
  };

  then(onFulfilled, onRejected) {
+   if (this.status === FULFILLED) {
      onFulfilled(this.value);
+   }
+   if (this.status === REJECTED) {
      onRejected(this.reason);
+  }
  }

异步操作

const Promise = require('./promise');

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功');
  }, 0);
});

p.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  },
);

当我们引入异步操作的时候,发现这里并不能打印出成功这显然是不符合预期的,这里也是因为我们调用then方法时,状态并没有立即改变,所以没有执行对应的操作,这里我们对then方法进行一下改造,使用发布-订阅的方式,将存储的方法统一存储,然后在改变状态的时候,统一处理这些方法。

  constructor(executor) {
+   this.onResolvedCallbacks = []; // 存储成功回调
+   this.onRejectedCallbacks = []; // 存储失败回调
  
    const resolve = (value) => {
      if (this.status === PENDING) {
        this.value = value;
        this.status = FULFILLED;
+       this.onResolvedCallbacks.forEach((fn) => fn());
      }
    };
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.reason = reason;
        this.status = REJECTED;
+       this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };
  
    try {
      executor(resolve, reject);
    } catch (e) {
      // 如果执行时发生了异常 那么这个异常就是失败的原因
      reject(e);
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value);
    }
    if (this.status === REJECTED) {
      onRejected(this.reason);
    }
+  if (this.status === PENDING) {
+    this.onResolvedCallbacks.push(() => {
+       onFulfilled(this.value);
+     });
+     this.onRejectedCallbacks.push(() => {
+       onRejected(this.reason);
+     });
+   }
  }

完整代码

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
  constructor(executor) {
    this.value = undefined; // 成功的值
    this.reason = undefined; // 失败的原因
    this.status = PENDING; // 默认的pending状态
    this.onResolvedCallbacks = []; // 存储成功回调
    this.onRejectedCallbacks = []; // 存储失败回调

    const resolve = (value) => {
      if (this.status === PENDING) {
        this.value = value;
        this.status = FULFILLED;
        this.onResolvedCallbacks.forEach((fn) => fn());
      }
    };
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.reason = reason;
        this.status = REJECTED;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (e) {
      // 如果执行时发生了异常 那么这个异常就是失败的原因
      reject(e);
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value);
    }
    if (this.status === REJECTED) {
      onRejected(this.reason);
    }
    if (this.status === PENDING) {
      this.onResolvedCallbacks.push(() => {
        onFulfilled(this.value);
      });
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason);
      });
    }
  }
}