异步编程模式
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
}