乐趣区

关于前端:循序渐进实现Promise

循序渐进实现 Promise

应用 JavaScript 循序渐进实现一个简略的 Promise,反对异步和 then 链式调用。

翻译并整顿自 Medium: Implementing a simple Promise in Javascript – by Zhi Sun

前言

在前端面试和日常开发中,常常会接触到 Promise。并且在现如今的很多面试中,也会常常被要求手写 Promise。

接下来,将应用 JavaScript 循序渐进实现一个简略的 Promise,反对异步和 then 链式调用。

剖析 Promise

Promise 对象用于示意一个异步操作的最终实现 (或失败)及其后果值,罕用来实现异步操作。

Promise 状态

Promise 有三种状态:

  • pending

    初始状态

  • fulfilled

    执行胜利后的状态

  • rejected

    执行失败后的状态

Promise 状态只能由 pending 扭转为 fulfilled 或者由 pending 扭转为rejected,Promise 状态扭转的这一过程被称为settled,并且,状态一旦扭转,后续就不会再次被扭转。

Promise 构造函数中的参数

Promise 构造函数接管一个函数参数executor,该函数接管两个参数:

  • resolve
  • reject

执行 resolve 会将 Promise 状态由 pending 扭转为 fulfilled,并触发then 办法中的胜利回调函数onFulfilled

执行 reject 会将 Promise 状态由 pending 扭转为 rejected,并触发then 办法中的失败回调函数onRejected

then 办法中的回调函数参数

then办法接管两个参数:

  • onFulfilled

    胜利回调函数,接管一个参数,即 resolve 函数中传入的值

  • onRejected

    失败回调函数,接管一个参数,即 reject 函数中传入的值

如果 Promise 状态变为fulfilled,就会执行胜利回调函数onFulfilled;如果 Promise 状态变为rejected,就会执行失败回调函数onRejected

实现 Promise

根底 Promise

首先,constructor接管一个函数 executor,该函数又接管两个参数,别离是resolvereject函数。

因而,须要在 constructor 中创立 resolvereject函数,并传入 executor 函数中。

class MyPromise {constructor(executor) {const resolve = (value) => {};

    const reject = (reason) => {};

    try {executor(resolve, reject);
    } catch (err) {reject(err);
    }
  }
}

其次,Promise 会依据状态,执行对应的回调函数。最开始的状态为 pending,当resolve 时,状态由 pending 变为 fulfilled;当reject 时,状态由 pending 变为rejected。并且,一旦状态变更后,就不会再次变更。

class MyPromise {constructor(executor) {
    this.state = 'pending';

    const resolve = (value) => {if (this.state === 'pending') {this.state = 'fulfilled';}
    };

    const reject = (reason) => {if (this.state === 'pending') {this.state = 'rejected';}
    };

    try {executor(resolve, reject);
    } catch (err) {reject(err);
    }
  }
}

Promise 状态变更后,会触发 then 办法中对应的回调函数。如果状态由 pending 变为 fulfilled,则会触发胜利回调函数,如果状态由pending 变为rejected,则会触发失败回调函数。

class MyPromise {constructor(executor) {
    this.state = 'pending';
    this.value = null;

    const resolve = (value) => {if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
      }
    };

    const reject = (reason) => {if (this.state === 'pending') {
        this.state = 'rejected';
        this.value = reason;
      }
    };

    try {executor(resolve, reject);
    } catch (err) {reject(err);
    }
  }

  then(onFulfilled, onRejected) {if (this.state === 'fulfilled') {onFulfilled(this.value);
    }

    if (this.state === 'rejected') {onRejected(this.value);
    }
  }
}

接下来能够写点测试代码测试一下性能

const p1 = new MyPromise((resolve, reject) => resolve('resolved'));
p1.then((res) => console.log(res), // resolved
  (err) => console.log(err)
);

const p2 = new MyPromise((resolve, reject) => reject('rejected'));
p2.then((res) => console.log(res),
  (err) => console.log(err) // rejected
);

然而,如果用以下代码测试,会发现什么也没有输入。

const p1 = new MyPromise((resolve, reject) => {setTimeout(() => resolve('resolved'), 1000);
});

p1.then((res) => console.log(res),
  (err) => console.log(err)
);

const p2 = new MyPromise((resolve, reject) => {setTimeout(() => reject('rejected'), 1000);
});

p2.then((res) => console.log(res),
  (err) => console.log(err)
);

这是因为在调用 then 办法时,Promise 仍处于 pending 状态。onFulfilledonRejected 回调函数都没有被执行。

因而,接下来须要反对异步。

反对异步的 Promise

为了反对异步,须要先保留 onFulfilledonRejected回调函数,一旦 Promise 状态变动,立即执行对应的回调函数。

⚠:这里有个细节须要留神,即 onFulfilledCallbacksonRejectedCallbacks是数组,因为 Promise 可能会被调用屡次,因而会存在多个回调函数。

class MyPromise {constructor(executor) {
    this.state = 'pending';
    this.value = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;

        this.onFulfilledCallbacks.forEach((fn) => fn(value));
      }
    };

    const reject = (value) => {if (this.state === 'pending') {
        this.state = 'rejected';
        this.value = value;

        this.onRejectedCallbacks.forEach((fn) => fn(value));
      }
    };

    try {executor(resolve, reject);
    } catch (err) {reject(err);
    }
  }

  then(onFulfilled, onRejected) {if (this.state === 'pending') {this.onFulfilledCallbacks.push(onFulfilled);
      this.onRejectedCallbacks.push(onRejected);
    }

    if (this.state === 'fulfilled') {onFulfilled(this.value);
    }

    if (this.state === 'rejected') {onRejected(this.value);
    }
  }
}

接下来能够用之前的测试代码测试一下性能

const p1 = new MyPromise((resolve, reject) => {setTimeout(() => resolve('resolved'), 1000);
});

p1.then((res) => console.log(res), // resolved
  (err) => console.log(err)
);

const p2 = new MyPromise((resolve, reject) => {setTimeout(() => reject('rejected'), 1000);
});

p2.then((res) => console.log(res),
  (err) => console.log(err) // rejected
);

然而如果用以下代码测试,会发现报错了。

const p1 = new MyPromise((resolve, reject) => {setTimeout(() => resolve('resolved'), 1000);
});

p1.then((res) => console.log(res),
  (err) => console.log(err)
).then((res) => console.log(res),
  (err) => console.log(err)
); // Uncaught TypeError: Cannot read property 'then' of undefined

这是因为第一个 then 办法并没有返回任何值,然而却又间断调用了 then 办法。

因而,接下来须要实现 then 链式调用。

反对 then 链式调用的 Promise

要想反对 then 链式调用,then办法须要返回一个新的 Promise。

因而,须要革新一下 then 办法,返回一个新的 Promise,等上一个 Promise 的 onFulfilledonRejected回调函数执行实现后,再执行新的 Promise 的 resolvereject函数。

class MyPromise {then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {if (this.state === 'pending') {this.onFulfilledCallbacks.push(() => {
          try {const fulfilledFromLastPromise = onFulfilled(this.value);
            resolve(fulfilledFromLastPromise);
          } catch (err) {reject(err);
          }
        });

        this.onRejectedCallbacks.push(() => {
          try {const rejectedFromLastPromise = onRejected(this.value);
            reject(rejectedFromLastPromise);
          } catch (err) {reject(err);
          }
        });
      }

      if (this.state === 'fulfilled') {
        try {const fulfilledFromLastPromise = onFulfilled(this.value);
          resolve(fulfilledFromLastPromise);
        } catch (err) {reject(err);
        }
      }

      if (this.state === 'rejected') {
        try {const rejectedFromLastPromise = onRejected(this.value);
          reject(rejectedFromLastPromise);
        } catch (err) {reject(err);
        }
      }
    });
  }
}

接下来能够用以下代码测试一下性能

const p1 = new MyPromise((resolve, reject) => {setTimeout(() => resolve('resolved'), 1000);
});

p1.then((res) => {console.log(res); // resolved
    return res;
  },
  (err) => console.log(err)
).then((res) => console.log(res), // resolved
  (err) => console.log(err)
);

const p2 = new MyPromise((resolve, reject) => {setTimeout(() => reject('rejected'), 1000);
});

p2.then((res) => console.log(res),
  (err) => {console.log(err); // rejected
    throw new Error('rejected');
  }
).then((res) => console.log(res),
  (err) => console.log(err) // Error: rejected
);

然而,如果改用以下代码测试,会发现第二个 then 办法中的胜利回调函数并没有按预期输入(‘resolved’),而是输入了上一个 then 办法的 onFulfilled 回调函数中返回的 Promise。

const p1 = new MyPromise((resolve, reject) => {setTimeout(() => resolve('resolved'), 1000);
});

p1.then((res) => {console.log(res); // resolved
        return new MyPromise((resolve, reject) => {setTimeout(() => resolve('resolved'), 1000);
    })
  },
  (err) => console.log(err)
).then((res) => console.log(res), // MyPromise {state: "pending"}
  (err) => console.log(err)
);

这是因为 onFulfilled/onRejected 回调函数执行完之后,只是简略的将 onFulfilled/onRejected 执行完返回的值传入 resolve/reject 函数中执行,并没有思考 onFulfilled/onRejected 执行完会返回一个新的 Promise 这种状况,所以第二次 then 办法的胜利回调函数中间接输入了上一次 then 办法的胜利回调函数中返回的 Promise。因而,接下来须要解决这个问题。

首先,能够将以上测试代码改成另一种写法,不便梳理思路。

const p1 = new MyPromise((resolve, reject) => {setTimeout(() => resolve('resolved'), 1000);
});

const p2 = p1.then((res) => {console.log(res);
    const p3 = new MyPromise((resolve, reject) => {setTimeout(() => resolve('resolved'), 1000);
    });

    return p3;
  },
  (err) => console.log(err)
);

p2.then((res) => console.log(res),
  (err) => console.log(err)
);

能够看到,一共有三个 Promise:

  • 第一个 Promise

    即通过 new 结构进去的 p1

  • 第二个 Promise

    即通过调用 then 办法返回的 p2

  • 第三个 Promise

    即在 p1.then 办法的胜利回调函数参数中返回的 p3

当初的问题是,调用 p2 的 then 办法时,p3 还处于 pending 状态。

要想实现 p2.then 办法中的回调函数能正确输入 p3 中 resolve/reject 之后的值,须要先等 p3 状态变动后再将变动后的值传入 p2 中的 resolve/reject 中即可。换句话说,三个 Promise 状态变动的先后顺序应该是 p1 –> p3 –> p2。

class MyPromise {then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {if (this.state === 'pending') {this.onFulfilledCallbacks.push(() => {
          try {const fulfilledFromLastPromise = onFulfilled(this.value);

            if (fulfilledFromLastPromise instanceof MyPromise) {fulfilledFromLastPromise.then(resolve, reject);
            } else {resolve(fulfilledFromLastPromise);
            }
          } catch (err) {reject(err);
          }
        });

        this.onRejectedCallbacks.push(() => {
          try {const rejectedFromLastPromise = onRejected(this.value);

            if (rejectedFromLastPromise instanceof MyPromise) {rejectedFromLastPromise.then(resolve, reject);
            } else {reject(rejectedFromLastPromise);
            }
          } catch (err) {reject(err);
          }
        });
      }

      if (this.state === 'fulfilled') {
        try {const fulfilledFromLastPromise = onFulfilled(this.value);

          if (fulfilledFromLastPromise instanceof MyPromise) {fulfilledFromLastPromise.then(resolve, reject);
          } else {resolve(fulfilledFromLastPromise);
          }
        } catch (err) {reject(err);
        }
      }

      if (this.state === 'rejected') {
        try {const rejectedFromLastPromise = onRejected(this.value);

          if (rejectedFromLastPromise instanceof MyPromise) {rejectedFromLastPromise.then(resolve, reject);
          } else {reject(rejectedFromLastPromise);
          }
        } catch (err) {reject(err);
        }
      }
    });
  }
}

最终版 Promise

最初,一个简略的 Promise 就实现了,反对异步和 then 链式调用。残缺代码如下:

class MyPromise {constructor(executor) {
    this.state = 'pending';
    this.value = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach((fn) => fn(value));
      }
    };

    const reject = (value) => {if (this.state === 'pending') {
        this.state = 'rejected';
        this.value = value;
        this.onRejectedCallbacks.forEach((fn) => fn(value));
      }
    };

    try {executor(resolve, reject);
    } catch (err) {reject(err);
    }
  }

  then(onFulfilled, onRejected) {return new Promise((resolve, reject) => {if (this.state === 'pending') {this.onFulfilledCallbacks.push(() => {
          try {const fulfilledFromLastPromise = onFulfilled(this.value);
            if (fulfilledFromLastPromise instanceof Promise) {fulfilledFromLastPromise.then(resolve, reject);
            } else {resolve(fulfilledFromLastPromise);
            }
          } catch (err) {reject(err);
          }
        });
        this.onRejectedCallbacks.push(() => {
          try {const rejectedFromLastPromise = onRejected(this.value);
            if (rejectedFromLastPromise instanceof Promise) {rejectedFromLastPromise.then(resolve, reject);
            } else {reject(rejectedFromLastPromise);
            }
          } catch (err) {reject(err);
          }
        });
      }

      if (this.state === 'fulfilled') {
        try {const fulfilledFromLastPromise = onFulfilled(this.value);
          if (fulfilledFromLastPromise instanceof Promise) {fulfilledFromLastPromise.then(resolve, reject);
          } else {resolve(fulfilledFromLastPromise);
          }
        } catch (err) {reject(err);
        }
      }

      if (this.state === 'rejected') {
        try {const rejectedFromLastPromise = onRejected(this.value);
          if (rejectedFromLastPromise instanceof Promise) {rejectedFromLastPromise.then(resolve, reject);
          } else {reject(rejectedFromLastPromise);
          }
        } catch (err) {reject(err);
        }
      }
    });
  }
}
退出移动版