JavaScript 自己实现 Promise

简介: JavaScript 自己实现 Promise

Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。

描述

一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled): 意味着操作成功完成。
  • 已拒绝(rejected): 意味着操作失败。

待定状态的 Promise 对象要么会通过一个值被兑现(fulfilled),要么会通过一个原因(错误)被拒绝(rejected)。当这些情况之一发生时,我们用 promise 的 then 方法排列起来的相关处理程序就会被调用。如果 promise 在一个相应的处理程序被绑定时就已经被兑现或被拒绝了,那么这个处理程序就会被调用,因此在完成异步操作和绑定处理方法之间不会存在竞争状态。

因为 Promise.prototype.thenPromise.prototype.catch 方法返回的是 promise, 所以它们可以被链式调用。

Promise 的链式调用

我们可以用 promise.then()promise.catch()promise.finally() 这些方法将进一步的操作与一个变为已敲定状态的 promise 关联起来。这些方法还会返回一个新生成的 promise 对象,这个对象可以被非强制性的用来做链式调用。

const myPromise = (new Promise(myExecutorFunc))
  .then(handleFullfilledA, handleRejectedA)
  .then(handleFullfilledB, handleRejectedB)
  .then(handleFullfilledC, handleRejectedC);

// 或许更好的写法

const myPromise = (new Promise(myExecutorFunc))
  .then(handleFullfilledA)
  .then(handleFullfilledB)
  .then(handleFullfilledC)
  .catch(handleRejectedAny);

过早地处理被拒绝的 promise 会对之后 promise 的链式调用造成影响。不过有时候我们因为需要马上处理一个错误也只能这样做。另一方面,在没有迫切需要的情况下,可以在最后一个.catch() 语句时再进行错误处理,这种做法更加简单。

构造函数

Promise()

创建一个新的 Promise 对象。该构造函数主要用于包装还没有添加 promise 支持的函数。

静态方法

Promise.all(iterable)

这个方法返回一个新的 promise 对象,该 promise 对象在 iterable 参数对象里所有的 promise 对象都成功的时候才会触发成功,一旦有任何一个 iterable 里面的 promise 对象失败则立即触发该 promise 对象的失败。这个新的 promise 对象在触发成功状态以后,会把一个包含 iterable 里所有 promise 返回值的数组作为成功回调的返回值,顺序跟 iterable 的顺序保持一致;如果这个新的 promise 对象触发了失败状态,它会把 iterable 里第一个触发失败的 promise 对象的错误信息作为它的失败错误信息。Promise.all 方法常被用于处理多个 promise 对象的状态集合。

Promise.allSettled(iterable)

等到所有 promises 都已敲定(settled)(每个 promise 都已兑现(fulfilled)或已拒绝(rejected))。
返回一个 promise,该 promise 在所有 promise 完成后完成。并带有一个对象数组,每个对象对应每个 promise 的结果。

Promise.any(iterable)

接收一个 Promise 对象的集合,当其中的一个 promise 成功,就返回那个成功的 promise 的值。

Promise.race(iterable)

当 iterable 参数里的任意一个子 promise 被成功或失败后,父 promise 马上也会用子 promise 的成功返回值或失败详情作为参数调用父 promise 绑定的相应句柄,并返回该 promise 对象。

Promise.reject(reason)

返回一个状态为失败的 Promise 对象,并将给定的失败信息传递给对应的处理方法。

Promise.resolve(value)

返回一个状态由给定 value 决定的 Promise 对象。如果该值是 thenable (即,带有 then 方法的对象),返回的 Promise 对象的最终状态由 then 方法执行决定;否则的话(该 value 为空,基本类型或者不带 then 方法的对象),返回的 Promise 对象状态为 fulfilled ,并且将该 value 传递给对应的 then 方法。通常而言,如果您不知道一个值是否是 Promise 对象,使用 Promise.resolve(value) 来返回一个 Promise 对象,这样就能将该 value 以 Promise 对象形式使用。

创建 Promise

Promise 对象是由关键字 new 及其构造函数来创建的。该构造函数会把一个叫做“处理器函数”(executor function)的函数作为它的参数。这个“处理器函数”接受两个函数——resolve 和 reject ——作为其参数。当异步任务顺利完成且返回结果值时,会调用 resolve 函数;而当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject 函数。

const myFirstPromise = new Promise((resolve, reject) => {
  // 做一些异步操作,最终会调用下面两者之一
  // resolve('Stuff worked!'); // fullfilled
  // or 
  // reject(Error('It broke')); // rejected
})

想要让某个函数拥有 promise 功能,只需要让他返回一个 promise 即可

function myAsyncFunction (url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
}

示例

let myFirstPromise = new Promise((resolve, reject) => {
  // 当异步代码执行成功时调用 resolve() ,失败时调用 reject()
  setTimeout(() => {
    resolve('Success!');
  }, 250);
});

myFirstPromise.then((successMessage) => {
  // successMessage 是 resolve() 的第一个参数
  // successMessage 参数不一定是字符串,可能是其他类型
  console.log('Yay! ' + successMessage);
});

实现

实现 resolve

要点:

  • 传参为一个 Promise 对象, 则直接返回它
  • 传参为一个 thenable 对象,返回的 Promise 对象会跟随这个对象,采用他的最终状态作为自己的状态
  • 其他情况,直接返回以该值为成功状态的 promise 对象
Promise.resolve = (param) => {
  if (param instanceof Promise) {
    return param;
  }
  return new Promise((resolve, reject) => {
    if (param && param.then && typeof param.then === 'function') {
      // param 状态变为成功会调用 resolve,将新的 Promise 的状态变为成功,反之亦然
      param.then(resolve, reject);
    } else {
      resolve(param);
    }
  });
};

实现 reject

Promise.reject 中传入的参数会作为一个 reason 原封不动地往下传

Promise.reject = (reason) => {
  return new Promise((resolve, reject) => {
    reject(reason);
  });
};

实现 finally

无论当前 Promise 是成功还是失败,调用 finally 之后都会执行 finally 中的函数,并且将原封不动地往下传

Promise.prototype.finally = function (callback) {
  this.then(value => {
    return Promise.resolve(callback()).then(() => {
      return value;
    });
  }, error => {
    return Promise.resolve(callback()).then(() => {
      throw error;
    });
  });
};

实现 all

要点:

  • 传入参数为一个空的可迭代对象时,直接进行 resolve
  • 如果参数中有一个 promise 失败,那么 Promise.all 返回的 promise 对象失败
  • 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组
Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let result = [];
    let index = 0;
    let length = promises.length;
    if (length === 0) {
      resolve(result);
      return;
    }

    for (let i = 0; i < length; i++) {
      // promises[i] 可能不是一个 promise 对象,所以要先进行一次 `resolve`
      Promise.resolve(promises[i]).then(data => {
        result[i] = data;
        index++;
        // 所有的都成功
        if (index === length) {
          resolve(result);
        }
      }).catch(err => {
        // 任一个失败
        reject(err);
      });
  });
};

实现 allsettled

接受的结果与入参时的 promise 实例一一对应,且结果的每一项都是一个对象,通知结果和值,对象内都有一个属性 status,用来明确知道对应的这个 promise 实例的状态(fullfilled 或 rejected), fullfilled 时,对象有 value 属性,rejected 时,对象有 reason 属性,对应两种状态的返回值

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(results => {
  console.log(results);
});
// [
//   { status: 'fulfilled', value: 42 },
//   { status: 'rejected', reason: -1 }
// ]

不论接受入参的 promise 本身的状态,会返回所有 promise 的结果。

function allSettled (iterable) {
  return new Promise((resolve, reject) => {
    function addElementToResult (i, elem) {
      result[i] = elem;
      elementCount++;
      if (elementCount === result.length) {
        resolve(result);
      }
    }
    let index = 0;
    for (const promise of iterable) {
      const currentIndex = index;
      promise.then(
        (value) => addElementToResult(currentIndex, { status: 'fulfilled', value }),
        (reason) => addElementToResult(currentIndex, { status: 'rejected', reason })
      );
      index++;
    }
    if (index === 0) {
      resolve([]);
      return;
    }
    let elementCount = 0;
    const result = new Array(index);
  });
}

实现 race

只要有一个 promise 执行完,直接 resolve 并停止执行

Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    let len = promises.length;
    if (len === 0) return;
    for (let i = 0; i < len; i++) {
      Promise.resolve(promises[i]).then(data => {
        resolve(data);
        return;
      }).catch(err => {
        reject(err);
        return;
      });
    }
  });
};

实现简版 Promise

function myPromise (constructor) {
  let self = this;
  self.status = 'pending'; // pending, fulfilled, rejected
  self.value = undefined;
  self.reason = undefined;

  function resolve (value) {
    if (self.status === 'pending') {
      self.status = 'fulfilled';
      self.value = value;
    }
  }

  function reject (reason) {
    if (self.status === 'pending') {
      self.status = 'rejected';
      self.reason = reason;
    }
  }

  try {
    constructor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

myPromise.prototype.then = function (onFullfilled, onRejected) {
  let self = this;
  switch (self.status) {
    case 'fulfilled':
      onFullfilled(self.value);
      break;
    case 'rejected':
      onRejected(self.reason);
      break;
    default:
      break;
  }
};

var p = new myPromise(function (resolve, reject) {
  resolve(1);
});

p.then(function (value) {
  console.log(value);
});
// expected output: 1

使用 class 实现

class myPromise {
  constructor (fn) {
    this.resolvedCallbacks = [];
    this.rejectedCallbacks = [];

    this.status = 'PENDING';
    this.value = undefined;

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

  resolve (value) {
    if (this.status === 'PENDING') {
      this.status = 'FULFILLED';
      this.value = value;

      this.resolvedCallbacks.map(cb => cb(value));
    }
  }

  reject (reason) {
    if (this.status === 'PENDING') {
      this.status = 'REJECTED';
      this.value = reason;

      this.rejectedCallbacks.map(cb => cb(reason));
    }
  }

  then (onFullfilled, onRejected) {
    if (this.status === 'FULFILLED') {
      onFullfilled(this.value);
    } else if (this.status === 'REJECTED') {
      onRejected(this.reason);
    } else {
      this.resolvedCallbacks.push(onFullfilled);
      this.rejectedCallbacks.push(onRejected);
    }
  }
}

Promise 详细实现

  • 可以把 Promise 看成一个状态机。初始时是 pending 状态,可以通过函数 resolvereject 将状态转变为 resolvedrejected。状态一旦改变,就不能再变化了。
  • then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同的实例的话,多个 then 调用就失去意义了。
  • 对于 then 来说,本质上可以把它看成是 flatMap
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

/**
 * Promise 接收一个函数参数,该函数会立刻执行
 * @param {Function} fn
 */
function MyPromise (fn) {
  let _this = this;
  _this.currentState = PENDING;
  _this.value = undefined;

  // 用于保存 then 中的回调,只有当 promise 状态为 pending 时才会缓存,且每个实例最多缓存一个
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  this.resolve = function (value) {
    if (value instanceof MyPromise) {
      // 如果 value 是个 Promise,递归执行
      return value.then(_this.resolve, _this.reject);
    }
    setTimeout(() => { // 异步执行,保证执行顺序
      if (_this.currentState === PENDING) {
        _this.currentState = RESOLVED;
        _this.value = value;
        _this.resolvedCallbacks.map(cb => cb(value));
      }
    });
  };

  this.reject = function (reason) {
    setTimeout(() => { // 异步执行,保证执行顺序
      if (_this.currentState === PENDING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.rejectedCallbacks.map(cb => cb(reason));
      }
    });
  };
  
  // 用于解决 new Promise(() => throw Error('error')) 的问题
  try {
    fn(_this.resolve, _this.reject);
  } catch (error) {
    _this._reject(error);
  }
}

MyPromise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  var promise2; // then 必须返回一个新的 Promise 实例

  // 如果类型不是函数需要忽略,同时也实现了透传
  onResolved = typeof onResolved === 'function' ? onResolved : value => value;
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

  if (self.currentState === RESOLVED) {
    return (promise2 = new MyPromise((resolve, reject) => {
      // 保证 onResolved 和 onRejected 异步执行
      setTimeout(function () {
        try {
         var x = onResolved(self.value);
         resolutionProcedure(promise2, x, resolve, reject);) 
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === REJECTED) {
    return (promise2 = new MyPromise((resolve, reject) => {
      setTimeout(function () {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === PENDING) {
    return (promise2 = new MyPromise((resolve, reject) => {
      self.resolvedCallbacks.push(() => {
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
      self.rejectedCallbacks.push(() => {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }
};

function resolutionProcedure (promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  // 如果 x 是个 Promise,状态为 pending 时需要等待,状态为 resolved 或 rejected 时直接执行
  if (x instanceof MyPromise) {
    if (x.currentState === PENDING) {
      x.then(function (value) {
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }

  // reject 或者 resolve 其中一个执行过的话,忽略其他的
  let called = false;
  // x 是个对象或者函数
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    // 如果不能取出 then,则直接 reject
    try {
      let then = x.then;
      if (typeof then === 'function') {
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            resolutionProcedure(promise2, y, resolve, reject);
          },
          e => {
            if (called) return;
            called = true;
            reject(e);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
相关文章
|
9天前
|
JavaScript 前端开发
JS中Promise的类式实现写法
JS中Promise的类式实现写法
|
1月前
|
前端开发 JavaScript API
JavaScript 中的 Promise
Promise 是 JavaScript 中用于处理异步操作的一种模式,它提供了一种比传统的回调函数更清晰、更易于管理和控制的方式来处理异步流程。
|
1月前
|
设计模式 前端开发 JavaScript
javascript 异常问题之Promise的未处理异常如何捕获
javascript 异常问题之Promise的未处理异常如何捕获
|
1月前
|
监控 前端开发 JavaScript
javascript 异常问题之在JavaScript中,Promise的异常如何处理
javascript 异常问题之在JavaScript中,Promise的异常如何处理
|
1月前
|
前端开发 JavaScript
javascript 异常问题之Promise异常如何捕获
javascript 异常问题之Promise异常如何捕获
|
1月前
|
前端开发 JavaScript
JavaScript异步编程4——Promise错误处理
JavaScript异步编程4——Promise错误处理
36 0
|
1月前
|
存储 JSON 前端开发
JavaScript异步编程3——Promise的链式使用
JavaScript异步编程3——Promise的链式使用
21 0
|
1月前
|
前端开发 JavaScript API
JavaScript异步编程2——结合XMLHttpRequest使用Promise
JavaScript异步编程2——结合XMLHttpRequest使用Promise
14 0
|
1月前
|
前端开发 JavaScript
JavaScript异步编程1——Promise的初步使用
JavaScript异步编程1——Promise的初步使用
22 0
|
1月前
|
前端开发 JavaScript
JavaScript——promise 是解决异步问题的方法嘛
JavaScript——promise 是解决异步问题的方法嘛
30 0