JavaScript 系列 - Promise

规范

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

特点

  • 对象的状态不受外界影响

    pending(进行中)、fulfilled(已成功)和 rejected(已失败)

    promises.png

  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果

    • pending 变为 fulfilled
    • pending 变为 rejected
    • resolved 或者 settled(已定型)
      • Promise 创建时就已经 resolved,可能是 pendingfulfilledrejected
    • 如果改变已经发生了,你再对 Promise 对象添加回调函数(then),也会立即得到这个结果
  • 避免嵌套的回调函数,控制异步操作更加容易

实现

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

function MyPromise(fn) {
  // 保存初始化状态
  var self = this;
  // 初始化状态
  this.state = PENDING;
  // 用于保存 resolve 或者 rejected 传入的值
  this.value = null;
  // 用于保存 resolve 的回调函数
  this.resolvedCallbacks = [];
  // 用于保存 reject 的回调函数
  this.rejectedCallbacks = [];
  // 状态转变为 resolved 方法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }

    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变,
      if (self.state === PENDING) {
        // 修改状态
        self.state = RESOLVED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.resolvedCallbacks.forEach((callback) => {
          callback(value);
        });
      }
    }, 0);
  }
  // 状态转变为 rejected 方法
  function reject(value) {
    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变
      if (self.state === PENDING) {
        // 修改状态
        self.state = REJECTED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.rejectedCallbacks.forEach((callback) => {
          callback(value);
        });
      }
    }, 0);
  }
  // 将两个方法传入函数执行
  try {
    fn(resolve, reject);
  } catch (e) {
    // 遇到错误时,捕获错误,执行 reject 函数
    reject(e);
  }
}

MyPromise.prototype.then = function (onResolved, onRejected) {
  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function (value) {
          return value;
        };

  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function (error) {
          throw error;
        };

  // 如果是等待状态,则将函数加入对应列表中
  if (this.state === PENDING) {
    this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }

  // 如果状态已经凝固,则直接执行对应状态的函数

  if (this.state === RESOLVED) {
    onResolved(this.value);
  }

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

缺点

  • 无法取消 Promise,一旦新建它就会立即执行,无法中途取消
  • 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部
  • 当处于 pending 状态时,无法得知目前进展到哪一个阶段

Promise 的链式调用

Promise.prototype.then()Promise.prototype.catch() 和 Promise.prototype.finally() 这些方法返回 Promise 可以链式调用。

一个 Promise 的终止状态决定了链中下一个 Promise 的 resolved 状态 fulfilled 传递给 then rejected 传递给 catch

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("foo");
  }, 300);
});
myPromise
  .then((value) => `${value} and bar`)
  .then((value) => `${value} and bar again`)
  .then((value) => `${value} and again`)
  .then((value) => `${value} and again`)
  .then((value) => {
    console.log(value);
  })
  .catch((err) => {
    console.error(err);
  });

JavaScript 语言允许在 Promise 的位置使用 thenable 对象

const aThenable = {
  then(onFulfilled, onRejected) {
    onFulfilled({
      // thenable 对象被兑现为另一个 thenable 对象
      then(onFulfilled, onRejected) {
        onFulfilled(42);
      },
    });
  },
};

Promise.resolve(aThenable); // 一个兑现值为 42 的 Promise

构造函数

Promise(executor)

  • 创建一个新的 Promise 对象
  • 该构造函数主要用于封装还没有添加 promise 支持的函数
function executor(resolveFunc, rejectFunc) {
  resolveFunc(value); // 解决时调用
  rejectFunc(reason); // 拒绝时调用
  // 通常,`executor` 函数用于封装某些接受回调函数作为参数的异步操作,比如上面的 `readFile` 函数
}

executor

  • executor 函数中的 return 语句不会影响 Promise 的履行值
  • executor 函数中抛出错误,resolveFuncrejectFunc 没有被调用,Promise 变为 rejected

resolveFunc

  • 如果它被调用时传入了本身 Promise 对象会被 reject 并抛出一个 TypeError 错误
  • 如果它使用一个非 thenable 的值(基本类型,或一个没有 then 属性或 then 属性不可调用的对象),则该 Promise 对象会被立即以该值兑现
const fulfilledResolved = new Promise((resolve, reject) => {
  resolve("外部");
});
// Promise {<fulfilled>: '外部'}
  • 如果它被调用时传入了一个 thenable 对象(包括另一个 Promise 实例)

    • resolve 函数是同步调用的
    • then 方法是异步调用的
    • 如果调用 then 方法时出现错误,则当前的 Promise 对象会被拒绝并抛出这个错误
    new Promise((resolve, reject) => {
      try {
        thenable.then(
          (value) => resolve(value),
          (reason) => reject(reason),
        );
      } catch (e) {
        reject(e);
      }
    });
    
  • 当前 promise 状态由后面的 promise 决定

// pendingResolved 状态是由 In 决定的
const pendingResolved = new Promise((resolveOuter, rejectOuter) => {
  const In = new Promise((resolveInner) => {
    resolveInner("内部");
  });
  resolveOuter(In);
});
  • resolveFunc 多次调用只会使用第一次的数据

rejectFunc

  • rejectFunc 类似 throw语句,reason 通常是一个 Error 实例

静态属性

Promise[Symbol.species]

返回用于构造从 Promise 方法返回值的构造函数

静态方法

Promise.all(iterable)

  • 接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。
  • 会在任何一个输入的 Promise 被拒绝时立即拒绝。

Promise.all() 方法是 promise 并发方法之一。它可用于聚合多个 Promise 的结果。通常在有多个相关的异步任务并且整个代码依赖于这些任务成功完成时使用

Promise.all 的异步性和同步性

只有当传递给 Promise.all 的 iterable 为空时,Promise.all 才会同步解决

// 传入一个已经 resolved 的 Promise 数组,以尽可能快地触发 Promise.all
const resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];

const p = Promise.all(resolvedPromisesArray);
// 立即打印 p 的值
console.log(p);

// 使用 setTimeout,我们可以在队列为空后执行代码
setTimeout(() => {
  console.log("队列现在为空");
  console.log(p);
});

// 按顺序打印:
// Promise { <state>: "pending" }
// 队列现在为空
// Promise { <state>: "fulfilled", <value>: Array[2] }

Promise.allSettled(iterable)

  • Promise.allSettled() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise
  • 有多个不依赖于彼此成功完成的异步任务时,或者你总是想知道每个 promise 的结果时,使用 Promise.allSettled()
Promise.allSettled([
  Promise.resolve(33),
  new Promise((resolve) => setTimeout(() => resolve(66), 0)),
  99,
  Promise.reject(new Error("一个错误")),
]).then((values) => console.log(values));

// [
//   { status: 'fulfilled', value: 33 },
//   { status: 'fulfilled', value: 66 },
//   { status: 'fulfilled', value: 99 },
//   { status: 'rejected', reason: Error: 一个错误 }
// ]

Promise.any(iterable)

  • Promise.any() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。
  • 输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。
  • 所有 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝。
async function fetchAndDecode(url, description) {
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error(`HTTP 错误!状态码:${res.status}`);
  }
  const data = await res.blob();
  return [data, description];
}

const coffee = fetchAndDecode("coffee.jpg", "Coffee");
const tea = fetchAndDecode("tea.jpg", "Tea");

Promise.any([coffee, tea])
  .then(([blob, description]) => {
    const objectURL = URL.createObjectURL(blob);
    const image = document.createElement("img");
    image.src = objectURL;
    image.alt = description;
    document.body.appendChild(image);
  })
  .catch((e) => {
    console.error(e);
  });

Promise.race(iterable)

Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。

第一个异步任务完成时,但不关心它的最终状态(即它既可以成功也可以失败)时,它就非常有用。

Promise.race 的异步性

Promise.race 总是异步的:即使 iterable 为空,它也永远不会同步地完成。

使用 Promise.race() 实现请求超时

const data = Promise.race([
  fetch("/api"),
  new Promise((resolve, reject) => {
    // 5 秒后拒绝
    setTimeout(() => reject(new Error("请求超时")), 5000);
  }),
])
  .then((res) => res.json())
  .catch((err) => displayError(err));

使用 Promise.race() 检测 Promise 的状态

function promiseState(promise) {
  const pendingState = { status: "待定" };

  return Promise.race([promise, pendingState]).then(
    (value) =>{
        return value === pendingState ? value : { status: "已兑现", value }
    },
    (reason) => {
        return { status: "已拒绝", reason }
    }
  );
}
const p1 = new Promise((res) => setTimeout(() => res(100), 100));
const p2 = new Promise((res) => setTimeout(() => res(200), 200));
const p3 = new Promise((res, rej) => setTimeout(() => rej(300), 100));

async function getStates() {
  console.log(await promiseState(p1));
  console.log(await promiseState(p2));
  console.log(await promiseState(p3));
}

console.log("开始状态:");
getStates();
setTimeout(() => {
  console.log("等待 100ms 后:");
  getStates();
}, 100);

Promise.reject(reason)

Promise.reject() 静态方法返回一个已拒绝(rejected)的 Promise 对象,拒绝原因为给定的参数。

使用 Promise.reject() 静态方法

Promise.reject(new Error("失败")).then(
  () => {
    // 不会被调用
  },
  (error) => {
    console.error(error); // Stacktrace
  },
);

reject 一个 promise 对象

与 Promise.resolve 不同,Promise.reject 方法不会重用已存在的 Promise 实例。它始终返回一个新的 Promise 实例,该实例封装了拒绝的原因(reason)。

const p = Promise.resolve(1);
const rejected = Promise.reject(p);
console.log(rejected === p); // false
rejected.catch((v) => {
  console.log(v === p); // true
});

在非 Promise 构造函数上调用 reject()

Promise.reject() 方法是一个通用方法。它可以在任何实现与 Promise() 构造函数相同签名的构造函数上调用。

class NotPromise {
  constructor(executor) {
    // “resolve”和“reject”函数的行为与原生 Promise 完全不同,但 `Promise.reject()` 方法以相同的方式调用它们。
    executor(
      (value) => console.log("已解决", value),
      (reason) => console.log("已拒绝", reason),
    );
  }
}

Promise.reject.call(NotPromise, "foo"); // 输出 "已拒绝 foo"

Promise.resolve(value)

Promise.resolve() 静态方法将给定的值转换为一个 Promise。

  • 如果该值本身就是一个 Promise,那么该 Promise 将被返回
  • 如果该值是一个 thenable 对象,Promise.resolve() 将调用其 then() 方法及其两个回调函数
  • 返回的 Promise 将会以该值兑现

该函数将嵌套的类 Promise 对象(例如,一个将被兑现为另一个 Promise 对象的 Promise 对象)展平,转化为单个 Promise 对象,其兑现值为一个非 thenable 值。

resolve 另一个 promise

Promise.resolve() 方法会重用已存在的 Promise 实例。如果它正在解决一个原生的 Promise,它将返回同一 Promise 实例,而不会创建一个封装对象。

const original = Promise.resolve(33);
const cast = Promise.resolve(original);
cast.then((value) => {
  console.log(`值:${value}`);
});
console.log(`original === cast ? ${original === cast}`);

// 按顺序打印:
// original === cast ? true
// 值:33

resolve thenable 对象

// Resolve 一个 thenable 对象
const p1 = Promise.resolve({
  then(onFulfill, onReject) {
    onFulfill("已兑现!");
  },
});
console.log(p1 instanceof Promise); // true,thenable 对象被转换为一个 Promise 对象

p1.then(
  (v) => {
    console.log(v); // "已兑现!"
  },
  (e) => {
    // 不会被调用
  },
);

// Thenable 在回调之前抛出异常
// Promise 被拒绝
const thenable = {
  then(onFulfilled) {
    throw new TypeError("抛出异常");
    onFulfilled("Resolving");
  },
};

const p2 = Promise.resolve(thenable);
p2.then(
  (v) => {
    // 不会被调用
  },
  (e) => {
    console.error(e); // TypeError: 抛出异常
  },
);

// Thenable 在回调 Promise 被解决之后抛出异常
const thenable = {
  then(onFulfilled) {
    onFulfilled("解决");
    throw new TypeError("Throwing");
  },
};

const p3 = Promise.resolve(thenable);
p3.then(
  (v) => {
    console.log(v); // "解决"
  },
  (e) => {
    // 不会被调用
  },
);

嵌套的 thenable 对象将被“深度展平”为单个 Promise 对象。

const thenable = {
  then(onFulfilled, onRejected) {
    onFulfilled({
      // 该 thenable 对象将兑现为另一个 thenable 对象
      then(onFulfilled, onRejected) {
        onFulfilled(42);
      },
    });
  },
};

Promise.resolve(thenable).then((v) => {
  console.log(v); // 42
});

在非 Promise 构造函数上调用 resolve()

class NotPromise {
  constructor(executor) {
    // “resolve”和“reject”函数的行为与原生 Promise 完全不同,但 `Promise.resolve()` 方法以相同的方式调用它们。
    executor(
      (value) => console.log("已解决", value),
      (reason) => console.log("已拒绝", reason),
    );
  }
}

Promise.resolve.call(NotPromise, "foo"); // 输出 "已解决 foo"

实例属性

这些属性定义在 Promise.prototype 上,由所有的 Promise 实例共享。

Promise.prototype.constructor

Promise() 构造函数创建 Promise 对象。它主要用于封装尚未支持 Promise 的基于回调的 API。

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();
  });
}

Promise.prototype[@@toStringTag]

@@toStringTag 属性的初始值为字符串 "Promise"。该属性用于 Object.prototype.toString()。

实例方法

Promise.prototype.catch(onRejected)

Promise 实例的 catch() 方法用于注册一个在 promise 被拒绝时调用的函数。此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。

在异步函数内部抛出的错误会像未捕获的错误一样:

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    throw new Error("未捕获的异常!");
  }, 1000);
});

p2.catch((e) => {
  console.error(e); // 永远不会被调用
});

在调用 resolve 之后抛出的错误会被忽略

const p3 = new Promise((resolve, reject) => {
  resolve();
  throw new Error("Silenced Exception!");
});

p3.catch((e) => {
  console.error(e); // 这里永远不会执行
});

如果 Promise 已兑现,catch() 不会被调用

// 创建一个不会调用 onReject 的 Promise
const p1 = Promise.resolve("调用下一个");

const p2 = p1.catch((reason) => {
  // 这里永远不会执行
  console.error("p1 的 catch 函数被调用了!");
  console.error(reason);
});

p2.then(
  (value) => {
    console.log("下一个 Promise 的 onFulfilled 函数被调用了");
    console.log(value); // 调用下一个
  },
  (reason) => {
    console.log("下一个 Promise 的 onRejected 函数被调用了");
    console.log(reason);
  },
);

Promise.prototype.finally(onFinally)

Promise 实例的 finally() 方法用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数。

let isLoading = true;

fetch(myRequest)
  .then((response) => {
    const contentType = response.headers.get("content-type");
    if (contentType && contentType.includes("application/json")) {
      return response.json();
    }
    throw new TypeError("Oops, we haven't got JSON!");
  })
  .then((json) => {
    /* 进一步处理 JSON */
  })
  .catch((error) => {
    console.error(error); // 这行代码也可能抛出错误,例如:when console = {}
  })
  .finally(() => {
    isLoading = false;
  });
Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    (value) => P.resolve(callback()).then(() => value),
    (reason) =>
      P.resolve(callback()).then(() => {
        throw reason;
      })
  );

Promise.prototype.then(onFulfilled, onRejected)

Promise 实例的 then() 方法最多接受两个参数:用于 Promise 兑现和拒绝情况的回调函数。

then() 的异步性

// 用一个已解决的 Promise——“resolvedProm”为例,
// 函数调用“resolvedProm.then(...)”立即返回一个新的 Promise,
// 但是其中的处理器“(value) => {...}”将被异步调用,正如打印输出所示。
// 新的 Promise 被赋值给“thenProm”,
// 并且 thenProm 将被解决为处理函数返回的值。
const resolvedProm = Promise.resolve(33);
console.log(resolvedProm);

const thenProm = resolvedProm.then((value) => {
  console.log(
    `在主堆栈结束后被调用。收到的值是:${value},返回的值是:${value + 1}`,
  );
  return value + 1;
});
console.log(thenProm);

// 使用 setTimeout,我们可以将函数的执行推迟到调用栈为空的时刻。
setTimeout(() => {
  console.log(thenProm);
});

// 按顺序打印:
// Promise {[[PromiseStatus]]: "resolved", [[PromiseResult]]: 33}
// Promise {[[PromiseStatus]]: "pending", [[PromiseResult]]: undefined}
// "在主堆栈结束后被调用。收到的值是:33,返回的值是:34"
// Promise {[[PromiseStatus]]: "resolved", [[PromiseResult]]: 34}
全部评论

相关推荐

喜欢喜欢喜欢:这是我见过最长最臭的简历
点赞 评论 收藏
分享
VirtualBool:都去逗他了?
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务