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

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

这篇文章将一步步实现 Promise,完全弄懂 Promise。面试

Promise 基本构成

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

最重要的还有个 then 的方法,then 函数能够传入两个函数做为参数,第一个函数用来获取异步操做的结果,第二个函数用来获取错误的缘由。promise

除此以外还须要 valuereason 存放 Promise 的结果或错误缘由。markdown

从上面这些信息能够转化为下面的代码:异步

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 函数有可能会调用屡次,那么能够用数组保存参数。oop

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 函数的链式调用。this

实现链式调用

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
相关文章
相关标签/搜索