前端基础

详解 Promise

约 1631 字大约 5 分钟

javascript

2020-11-22

概述

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

Promise(resolver : (resolve, reject) => void)

Promise 接受一个函数resolver作为参数,包装需要执行的处理程序,当处理结果为成功时,将成功的返回值作为参数调用resolve 方法, 如果失败,则将失败原因作为参数调用reject方法。

示例

const promise = new Promise(function (resolve, reject) {
  setTimeout(() => {
    // do something
    if (Math.random() * 10 > 5) {
      resolve({ status: 'success', data: '' })
    } else {
      reject(new Error('error'))
    }
  }, 500)
})

Promise状态

Promise 创建后,必然处于以下几种状态

  • pending : 待定状态,既没有被兑现,也没有被拒绝
  • fulfilled : 操作成功。
  • rejected : 操作失败。

当状态从 pending 更新为fulfilledrejected 后,就再也不能变更为其他状态。

Promise 实例方法

.then(onFulfilled, onRejected)

then() 接收两个函数参数(也可以仅接收一个函数参数 onFulfilled)。

  • onFulfilled 函数参数,表示当 promise的状态从 pending 更新为fulfilled 时触发,并将成功的结果 value 作为onFulfilled函数的参数。
  • onRejected 函数参数,表示当promise的状态从 pending 更新为rejected 时触发,并将失败的原因 reason 作为 onRejected函数的参数。

then() 方法返回的结果会被包装为一个新的promise实例。

.catch(onRejected)

catch() 可以相当于 .then(null, onRejected),即仅处理当promise的状态从 pending 更新为rejected 时触发。

.finally(onFinally)

表示promise的状态无论是从pengding更新为fulfilledrejected,当所有的 then() 和 catch() 执行完成后,最后会执行 finally() 的回调。

由于无法知道promise的最终状态,onFinally 回调函数不接收任何参数,它仅用于无论最终结果如何都要执行的情况。

promise
  .then(function (res) {
    console.log(res) // { status: 'success', data: '' }
  })
  .catch(function (reason) {
    console.log(reason) // error: error
  })
  .finally(() => {
    // do something
  })

链式调用

使用Promise的一个优势是,可以链式调用的方式,执行多个then()/catch()方法。 且回调函数允许我们返回任何值,返回的值将会被包装为一个 promise实例,将值传给下一个then()/catch()方法。

promise
  .then(res => {
    res.data = { a: 2 }
    return res
  })
  .then(res => {
    console.log(res) // { status: 'success', data: { a: 2 } }
    throw new Error('cath error')
  })
  .catch(reason => {
    console.log(reason) // error: cath error
  })

Promise代码实现

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
const microtask = globalThis.queueMicrotask || ((cb) => setTimeout(cb, 0));

function LikePromise(resolver) {
  if (typeof resolver !== "function") {
    throw new TypeError(`Promise resolver ${resolver} is not a function`);
  }
  this.state = PENDING;
  this.value = undefined;
  this.reason = undefined;
  this.fulfillQueue = [];
  this.rejectQueue = [];

  const that = this;

  function reject(reason) {
    if (that.state === PENDING) {
      that.state = REJECTED;
      that.reason = reason;
      that.rejectQueue.forEach((cb) => cb(reason));
    }
  }

  function resolve(value) {
    if (that.state === PENDING) {
      that.state = FULFILLED;
      that.value = value;
      that.fulfillQueue.forEach((cb) => cb(value));
    }
  }

  try {
    resolver(resolve, reject);
  } catch (e) {
    reject(e);
  }
}

function resolvePromise(promise, x, resolve, reject) {
  if (promise === x) {
    reject(new TypeError("chaining cycle"));
  } else if (x !== null && (typeof x === "object" || typeof x === "function")) {
    let used = false;
    try {
      const then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (used) return;
            used = true;
            resolvePromise(promise, y, resolve, reject);
          },
          (r) => {
            if (used) return;
            used = true;
            reject(r);
          }
        );
      } else {
        if (used) return;
        used = true;
        resolve(x);
      }
    } catch (e) {
      if (used) return;
      used = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}

LikePromise.prototype.then = function (onFulfilled, onRejected) {
  onFulfilled =
    typeof onFulfilled === "function" ? onFulfilled : (value) => value;
  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : (reason) => {
          throw reason;
        };
  const that = this;
  const promise = new LikePromise((resolve, reject) => {
    if (that.state === FULFILLED) {
      microtask(() => {
        try {
          const x = onFulfilled(that.value);
          resolvePromise(promise, x, resolve, reject);
        } catch (e) {
          reject(e);
        }
      });
    } else if (that.state === REJECTED) {
      microtask(() => {
        try {
          const x = onRejected(that.reason);
          resolvePromise(promise, x, resolve, reject);
        } catch (e) {
          reject(e);
        }
      });
    } else {
      that.fulfillQueue.push(() => {
        microtask(() => {
          try {
            const x = onFulfilled(that.value);
            resolvePromise(promise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      });
      that.rejectQueue.push(() => {
        microtask(() => {
          try {
            const x = onRejected(that.reason);
            resolvePromise(promise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      });
    }
  });
  return promise;
};

LikePromise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected);
};

LikePromise.prototype.finally = function (onFinally) {
  return this.then(
    (value) => LikePromise.resolve(onFinally()).then(() => value),
    (reason) =>
      LikePromise.resolve(onFinally()).then(() => {
        throw reason;
      })
  );
};

LikePromise.resolve = function (value) {
  return new LikePromise((resolve) => resolve(value));
};

LikePromise.reject = function (reason) {
  return new LikePromise((_, reject) => 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.reject(reason)

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

Promise.all(promises)

all() 允许传入一组promise实例,并返回一个新的promise实例。

promises并发执行,并且当这组promises的最终状态均更新为fulfilled时,才触发返回的promise实例的onFulfilled, 并将这组promises的执行结果,已promises的定义顺序,以数组的形式传给onFulfilled。 如果其中某个promise的最终状态更新为rejected,则立即触发返回的promise实例的onRejected

示例:

const promises = [
  Promise.resolve({ a: 1}),
  new Promise((resolve) => {
    setTimeout(() => {
      resolve({ b: 1 })
    }, 0)
  })
]
Promise.all(promises).then(res => {
  console.log(res) // [ { a: 1}, { b: 1 } ]
})

手写Promise.all 实现代码

function promiseAll(promises) {
  promises = promises || []
  let length = promises.length
  if (length === 0) return Promise.resolve([])
  let count = 0
  const list = []
  return new Promise((resolve, reject) => {
    const resolveFn = (res, index) => {
      list[index] = res
      count ++
      if (count >= length) {
        resolve(list)
      }
    }
    promises.forEach((item, i) => {
      if (item instanceof Promise) {
        item.then(res => resolveFn(res, i), reject)
      } else {
        resolveFn(item, i)
      }
    })
  })
}

Promise.allSettled

allSettled(promises) 允许传入一组promise实例,并返回一个新的promise对象。

当这组promises的状态从pending 都更新到最终状态、无论最终状态是 fulfilledrejected时,触发返回的promise的onfulfilled

onfulfilled 回调函数,根据promises定义的顺序,将执行结果以 { status: string, [value|reason]: any }[] 的形式作为参数传入。

示例

const promises = [
  Promise.resolve({ a: 1}),
  Promise.reject('reason')
]
Promise.allSettled(promises).then(res => {
  console.log(res) // [ { status: 'fulfilled’, value: { a: 1 } }, { status: 'rejected', reason: 'reason' }  ]
})

手写Promise.allSettled 实现代码

function promiseAllSettled(promises) {
  promises = promises || []
  let length = promises.length
  if (length === 0) return Promise.resolve([])
  let count = 0
  const list = []
  return new Promise((resolve) => {
    const resolveFn =  (res, index, status) => {
      list[index] = { status }
      if (status === 'fulfilled') {
        list[index].value = res
      } else {
        list[index].reason = res
      }
      count ++
      if (count >= length) {
        resolve(list)
      }
    }
    promises.forEach((item, i) => {
      if (item instanceof Promise) {
        item.then(
          res => resolveFn(res, i, 'fulfilled'),
          reason => resolveFn(reason, i, 'rejected')
        )
      } else {
        resolveFn(item, i, 'fulfilled')
      }
    })
  })
}

Promise.race

Promise.race(promises) 接收一组promise实例作为参数,并返回一个新的promise对象。

当这组promises中的任意一个promise的状态从pending更新为fulfilledrejected时,返回的promise对象将会把该promise的成功返回值或者失败原因 作为参数调用返回的promise的onFulfilledonRejected

示例

const promises = [
  new Promise((resolve) => {
    setTimeout(() => {
      resolve('timeout')
    }, 500)
  }),
  Promise.resolve('resolve')
]

Promise.race(promises).then(res => {
  console.log(res) // resolve
})

手写Promise.race 实现代码

function promiseRace(array) {
  array = array || []
  return new Promise((resolve, reject) => {
    array.forEach(item => {
      if (item instanceof Promise) {
        item.then(resolve, reject)
      } else {
        resolve(item)
      }
    })
  })
}

参考资料

Promise A+ 规范

MDN Promise

MDN 使用Promise