【面试题解析】手动实现Promise

·  阅读 4373

前端面试的时候,经常能看到这样一道题,实现一个Promise

这篇文章将一步步实现 Promise,彻底弄懂 Promise。

Promise 基本构成

平时使用 Promise 我们可以知道 Promise 存在三种状态 Pending、Resolve、Reject,在 new Promise 时需要传入一个函数, 参数为 resolvereject 的函数,这两个函数用来改变 Promise 的状态。

最重要的还有个 then 的方法,then 函数可以传入两个函数作为参数,第一个函数用来获取异步操作的结果,第二个函数用来获取错误的原因。

除此之外还需要 valuereason 存放 Promise 的结果或错误原因。

从上面这些信息可以转化为下面的代码:

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = null;
    this.reason = null;

    function resolve (value) {
      this.status = RESOLVED;
      this.value = value;
    };

    function reject (reason) {
      this.status = REJECTED;
      this.reason = reason;
    };

    executor(resolve.bind(this), reject.bind(this));
  }

  then(onFulfilled, onRejected) {
    if (this.status === RESOLVED) {
      onFulfilled(this.value);
    }

    if (this.status === REJECTED) {
      onRejected(this.reason);
    }
  }
}
复制代码

Promise 的状态只允许修改一次,那么 resolvereject 需要加上状态判断。

function resolve (value) {
  if (this.status !== PENDING) return;
  this.status = RESOLVED;
  this.value = value;
};

function reject (reason) {
  if (this.status !== PENDING) return;
  this.status = REJECTED;
  this.reason = reason;
};
复制代码

在调用 then 函数时,Promise 的状态有可能还是 Pending 的状态,这时需要将 then 函数的两个参数进行保存,状态改变时在进行调用。then 函数有可能会调用多次,那么可以用数组保存参数。

class Promise {
  constructor(executor) {
    // ...
    this.resolveCbs = [];
    this.rejectCbs = [];
    function resolve (value) {
      // ...
      this.resolveCbs.map(fn => fn(this.value));
    };

    function reject (reason) {
      // ...
      this.rejectCbs.map(fn => fn(this.reason));
    };
  }

  then(onFulfilled, onRejected) {
    // ...
    if (this.status === PENDING) {
      this.resolveCbs.push(onFulfilled);
      this.rejectCbs.push(onRejected);
    }
  }
}
复制代码

写到这里,一个最基本的 Promise 就可以使用了。

 new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 500);
}).then(res => {
  console.log(res);
});
复制代码

上面的代码虽然完成了最基本的 Promise,但是还未实现 then 函数的链式调用。

实现链式调用

new Promise((resolve, reject) => {
  // ...
}).then(res => {
  // ...
}).then(res => {
  // ...
})
复制代码

链式调用也是 Promise 的重点所在,因为有了链式调用,才能避免回调地狱的问题。接下来就来一步步实现。

then 是 Promise 的方法,为了能够继续调用 then 函数,需要 then 函数返回一个新的 Promise。

onFulfilledonRejected 的返回值有可能也是一个 Promise,那么需要等待 Promise 执行完的结果传递给下一个 then 函数。如果返回的不是 Promise,就可以将结果传递给下一个 then 函数。

then 函数进行如下修改,resolvePromise 另外实现。

class Promise {
  // ...
  then(onFulfilled, onRejected) {
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === RESOLVED) {
        let x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      }

      if (this.status === REJECTED) {
        let x = onRejected(this.reason);
        resolvePromise(promise2, x, resolve, reject);
      }

      if (this.status === PENDING) {
        this.resolveCbs.push(() => {
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        });

        this.rejectCbs.push(() => {
          let x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        });
      }
    });

    return promise2;
  }
}
复制代码

实现 resolvePromise

then(onFulfilled, onRejected) {
  function resolvePromise (promise2, x, resolve, reject) {
    if (promise2 === x) {
      // 不允许 promise2 === x; 避免自己等待自己
      return reject(new TypeError('Chaining cycle detected for promise'));
    }

    // 防止重复调用
    let called = false;

    try {
      if (x instanceof Promise) {
        let then = x.then;
        // 第一个参数指定调用对象
        // 第二个参数为成功的回调,将结果作为 resolvePromise 的参数进行递归
        // 第三个参数为失败的回调
        then.call(x, y => {
          if (called) return;
          called = true;
          // resolve 的结果依旧是 Promise 那就继续解析
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if (called) return;
          called = true;
          reject(err);
        });
      } else {
        resolve(x);
      }
    } catch (e) {
      reject(e);
    }
  }

  // ...
}
复制代码

优化 then 函数

then 函数的 onFulfilledonRejected 参数允许不传.

Promise/A+ 规范要求 onFulfilledonRejected 不能被同步调用,可以使用 setTimeout 改为异步调用。

then(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => { return v };
  onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; };

  function resolvePromise (promise2, x, resolve, reject) {...}

  let promise2 = new Promise((resolve, reject) => {
    function fulfilled () {
      setTimeout(() => {
        let x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      }, 0);
    };

    function rejected () {
      setTimeout(() => {
        let x = onRejected(this.reason);
        resolvePromise(promise2, x, resolve, reject);
      }, 0);
    }

    if (this.status === RESOLVED) {
      fulfilled.call(this);
    }

    if (this.status === REJECTED) {
      rejected.call(this);
    }

    if (this.status === PENDING) {
      this.resolveCbs.push(fulfilled.bind(this));
      this.rejectCbs.push(rejected.bind(this));
    }
  });

  return promise2;
}
复制代码

catch 等方法实现

class Promise {
  // ...
  catch(fn) {
    this.then(null, fn);
  }

  static resolve (val) {
    return new Promise((resolve) => {
      resolve(val);
    });
  }

  static reject (val) {
    return new Promise((resolve, reject) => {
      reject(val);
    });
  }

  static race(promises) {
    return new Promise((resolve, reject) => {
      promises.map(promise => {
        promise.then(resolve, reject);
      });
    });
  }

  static all(promises) {
    let arr = [];
    let i = 0;
    return new Promise((resolve, reject) => {
      promises.map((promise, index) => {
        promise.then(data => {
          arr[index] = data;
          if (++i === promises.length) {
            resolve(arr);
          }
        }, reject);
      })
    })
  }
}

复制代码

参考文章

BAT前端经典面试问题:史上最最最详细的手写Promise教程


如果你喜欢我的文章,希望可以关注一下我的公众号【前端develop】

前端develop

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改