异步编程模式

JavaScript 异步编程模式

Promise 编排

异常处理

最容易犯的错误,没有使用 catch 去捕获 then 里抛出的报错:

// snippet1
somePromise()
  .then(function() {
    throw new Error("oh noes");
  })
  .catch(function(err) {
    // I caught your error! :)
  });

// snippet2
somePromise().then(
  function resolve() {
    throw new Error("oh noes");
  },
  function reject(err) {
    // I didn't catch your error! :(
  }
);

这里的问题在于 snippet2 在 function resolve 中出错时无法捕获。而 catch 则可以。

下面的两个示例返回结果是不一样的:

// example1
Promise.resolve("foo")
  .then(Promise.resolve("bar"))
  .then(function(result) {
    console.log(result); // foo
  });
// example2
Promise.resolve("foo")
  .then(function() {
    return Promise.resolve("bar");
  })
  .then(function(result) {
    console.log(result); // bar
  });

example2 改变了返回值,因而 result 发生了变化。

引入此文中对于 async/await 的循环的写法

async function waitAndMaybeReject() {
  // Wait one second
  await new Promise(r => setTimeout(r, 1000));
  // Toss a coin
  const isHeads = Boolean(Math.round(Math.random()));

  if (isHeads) return "yay";
  throw Error("Boo!");
}

async function foo() {
  try {
    // Wait for the result of waitAndMaybeReject() to settle,
    // and assign the fulfilled value to fulfilledValue:
    const fulfilledValue = await waitAndMaybeReject();
    // If the result of waitAndMaybeReject() rejects, our code
    // throws, and we jump to the catch block.
    // Otherwise, this block continues to run:
    return fulfilledValue;
  } catch (e) {
    return "caught";
  }
}

Promises 是为了让异步代码也能保持这些同步代码的属性:扁平缩进和单异常管道。在 ES6 之前,存在着很多的 Promise 的支持库,譬如著名的 q 以及 jQuery 中都有着对于 Promise 模式的内在的实现。在 ES6 之后,笔者是推荐仅使用 ES6 提供的 Promise 对象。

避免异步混淆与性能损耗

async/await 允许我们以同步的方式编写异步代码,不过这种方式也可能带来额外的性能损耗,或者混淆现有的代码逻辑。

(async () => {
  const pizzaData = await getPizzaData(); // async call
  const drinkData = await getDrinkData(); // async call
  const chosenPizza = choosePizza(); // sync call
  const chosenDrink = chooseDrink(); // sync call
  await addPizzaToCart(chosenPizza); // async call
  await addDrinkToCart(chosenDrink); // async call
  orderItems(); // async call
})();

所有这些语句一个接一个地执行。这里没有并行逻辑。细想一下:为什么我们要在获取饮料列表之前等待获取披萨列表?我们应该尝试一起获取这两个列表。然而,当我们需要选择一个披萨的时候,我们确实需要在这之前获取披萨列表。对于饮料来说,道理类似。因此,我们可以总结如下:披萨相关的任务和饮料相关的任务可以并行发生,但是披萨(或饮料)各自的相关步骤需要按顺序(一个接一个地)发生。

async function orderItems() {
  const items = await getCartItems(); // async call
  const noOfItems = items.length;
  for (const i = 0; i < noOfItems; i++) {
    await sendRequest(items[i]); // async call
  }
}

找出那些依赖其它语句执行的语句,将相互依赖的语句包在一个 async 函数中,然后并行执行这些 async 函数。可以利用事件循环来并行运行这些异步非阻塞函数。这样做的两种常见模式是 先返回 promise 和 Promise.all 方法。

async function selectPizza() {
  const pizzaData = await getPizzaData(); // async call
  const chosenPizza = choosePizza(); // sync call
  await addPizzaToCart(chosenPizza); // async call
}

async function selectDrink() {
  const drinkData = await getDrinkData(); // async call
  const chosenDrink = chooseDrink(); // sync call
  await addDrinkToCart(chosenDrink); // async call
}

(async () => {
  const pizzaPromise = selectPizza();
  const drinkPromise = selectDrink();
  await pizzaPromise;
  await drinkPromise;
  orderItems(); // async call
})()(
  // Although I prefer it this way

  async () => {
    Promise.all([selectPizza(), selectDrink()]).then(orderItems); // async call
  }
)();

在第二个例子中,我们需要处理不确定数量的 promise。处理这种情况超级简单:我们只用创建一个数组,然后将所有的 promise 放进数组中。然后使用 Promise.all(),我们就可以并行等待所有的 promise 处理。

async function orderItems() {
  const items = await getCartItems(); // async call
  const noOfItems = items.length;
  const promises = [];
  for (const i = 0; i < noOfItems; i++) {
    const orderPromise = sendRequest(items[i]); // async call
    promises.push(orderPromise); // sync call
  }
  await Promise.all(promises); // async call
}
上一页
下一页