zoukankan      html  css  js  c++  java
  • 如何避免 async/await 地狱

    简评:async/await 写着很爽,不过要注意这些问题。

    async/await 让我们摆脱了回调地狱,但是这又引入了 async/await 地狱的问题。

    什么是 async/await 地狱

    在 Javascript 中进行异步编程的时候,人们总是使用很多 await 语句,很多时候我们的语句并不需要依赖于之前的语句,这样就会导致性能问题。

    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
    })()
    这段代码运行没有问题。但是不是一个好的实现,因为这增加了不必要的等待。

    说明

    我们已经将我们的代码封装在异步 IIFE 中,按照下面的顺序执行:

    得到披萨名单
    获取饮料列表
    从列表中选择一个披萨
    从列表中选择一种饮料
    将选中的披萨加入购物车
    将选择的饮品加入购物车
    订购购物车中的物品

    问题

    这里有个问题为什么从列表中选择披萨这个动作要等待获取饮料列表?这两个是没什么关联的操作。其中的关联操作有两组:

    获取披萨列表 -》 选择披萨 -》 选择披萨加入购物车

    获取饮料列表 -》 选择饮料 -》 选择饮料加入购物车

    这两组操作应该是并发执行的。

    再来看一个更差的例子

    这个 Javascript 代码片段将购物车中的商品并发出订购请求。

    async function orderItems() {
    const items = await getCartItems() // async call
    const noOfItems = items.length
    for(var i = 0; i < noOfItems; i++) {
    await sendRequest(items[i]) // async call
    }
    }
    这种情况 for 循环必须等待 sendRequest() 函数完成才能继续下一次迭代。但是,我们并不需要等待。我们希望尽快发送所有请求。然后我们可以等待所有请求完成。

    现在你应该已经对 async/await 地狱有跟多的了解,现在我们再来考虑一个问题

    如果我们忘记 await 关键字会怎么样?

    如果在调用异步函数忘记使用 await,这意味着执行该功能不需要等待。异步函数将直接返回一个 promise,你可以稍后使用。

    (async () => {
    const value = doSomeAsyncTask()
    console.log(value) // an unresolved promise
    })()
    或者是程序不清楚你想要等待函数执行完,直接退出不会完成这个异步任务。所以我们需要使用 await 这个关键字。

    promise 有一个有趣的属性,你可以在某行代码中获取 promise,然后在其他地方中等待它 resolve,这是解决 async/await 地狱的关键。

    (async () => {
    const promise = doSomeAsyncTask()
    const value = await promise
    console.log(value) // the actual value
    })()
    如你所见 doSomeAsyncTask 直接返回一个 Promise 同时这个异步函数 doSomeAsyncTask 已经开始执行,为了得到 doSomeAsyncTask 的返回值,我们需要 await 来告诉

    应该如何避免 async/await 地狱

    首先我们需要知道哪些命名是有前后依赖关系的。
    然后将有依赖关系的系列操作进行分组合并成一个异步操作。
    同时执行这些异步函数。
    我们来重写这写例子:

    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
    })()
    我们将语句分成两个函数。在函数内部,每个语句都依赖于前一个语句的执行。然后我们同时执行这两个函数 selectPizza()和selectDrink() 。

    在第二个例子中我们需要处理未知数量的 Promise。处理这个问题非常简单,我们只需要创建一个数组将所有 Promise 存入其中,使用 Promise.all() 方法并行执行:

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

  • 相关阅读:
    [原创]FineUI秘密花园(二十九) — 用户控件概述
    FineUI v3.2.4发布了!
    [原创]FineUI秘密花园(二十六) — 选项卡控件概述
    [原创]FineUI秘密花园(三十) — ViewState与XState (本系列文章最后一篇,兑现承诺,现提供完整PDF版下载!)
    FineUI 将不再内置 ExtJS (严格遵守 ExtJS 的开源规则)
    fineui.com 可以正常访问了
    [原创]22行JavaScript代码实现QQ群成员提取器,绿色、环保、无病毒!
    [原创]如何在FineUI中集成jQuery UI的AutoComplete组件
    《计算机程序的解释和构造》(SICP)学习2-对系统结构的两种不同的“世界观”
    报表开发最后一计使用iTextSharp来开发报表
  • 原文地址:https://www.cnblogs.com/jpush88/p/8939956.html
Copyright © 2011-2022 走看看