【问题标题】:Javascript Awaits not executing in correct order within Async functionJavascript Awaits 在异步函数中未按正确顺序执行
【发布时间】:2021-12-10 04:11:53
【问题描述】:

我知道他们在这里有很多关于 async 和 await 和 promises 的问题,我查看了所有这些问题,但我似乎无法让任何解决方案适用于我的具体情况。我确定这是因为我对 Promise 和回调的使用感到困惑。我要做的就是按顺序触发这 3 个 api 调用。这是我最近的尝试:-

    async function getData() {
        var firstCall = api.getBaseInfo(name, num, (error, response) => {
            console.log(response)
        });
        var secondCall = api.getMainInfo(address, company, { type:'init' , family: name, id: num }, (error, response) => {
            console.log(response) 
        });
        var thirdCall =  api.getBackData(num, (error, orders, genre) => {
            console.log(orders)
        });
        await firstCall
        await secondCall
        await thirdCall
    }
    getData()

我尝试了各种包含 .then、多个异步函数、普通函数的变体,但我所做的一切只是让输出到达,就好像所有 3 个调用同时触发一样。我不介意使用什么方法,我只想知道如何安排我的代码,以便每次调用都在前一个调用完成后执行。

【问题讨论】:

  • "输出到达好像所有 3 个调用同时触发" - 这仅仅是因为控制台是异步的。查看输出,您会看到一个蓝色的小i 图标。
  • 当我在每个 api 调用开始时使用 await 时,我得到的结果与以这种方式设置时完全相同 - 这就是我最初的编码方式,但将其更改为在我在这里看到建议的解决方案后格式化。但是,是的,显然你说的是正确的
  • 您需要做的就是从getData() 函数中收集并返回您需要的内容并使用.then() -> getData().then(console.log);
  • (1) 你确定 api.getSomething(...) 表达式返回 Promise 吗(这是 await 有任何目的所必需的)?每个表达式都包含一个 nodeback,因此似乎没有返回 Promise(不保证任何一种方式)。尝试运行测试以查看使用/不使用 nodebacks 时返回的内容。
  • (2) 一旦有了返回 Promise 的 api.getSomething(...) 表达式,请考虑时机。三个await ... 行会影响三个api调用的时间吗?答案是否定的,因为已经进行了 api 调用。那么要让三个 api 调用“在前一个完成后执行”,等待属于哪里?需要等待每个api调用的响应,因此将三个调用的形式更改为var result_1 = await api.getSomethng(...);(不带nodeback),并清除现有的三个await ...Call行。瞧!

标签: javascript async-await promise callback arrow-functions


【解决方案1】:

根据您的描述,您的函数似乎没有返回 Promises。相反,它们通过回调(您在其中编写 console.log)处理异步行为。没有承诺 async/await 什么都不做。您的代码没有抛出任何错误的原因是 await 旨在静默忽略任何不是承诺的内容,以支持可能返回或不返回 Promise 的函数(例如,在某些情况下,函数返回 Promise 但在其他情况下,它返回null)

为了按顺序执行您的功能,您需要等待每个功能完成。我们知道他们完成的一个地方是在他们的回调中。所以你需要重写你的代码如下:

function getData() {
    api.getBaseInfo(name, num, (error, response) => {
        console.log(response);

        api.getMainInfo(address, company, { type:'init' , family: name, id: num }, (error, response) => {
            console.log(response);

            api.getBackData(num, (error, orders, genre) => {
                console.log(orders)
            });
        });
    });
}
getData();

这样每个函数都可以在执行下一个函数之前完成。

承诺

虽然上述代码有效,但更复杂的代码会产生非常深的嵌套。嵌套可以通过使用命名函数而不是匿名函数来解决。事实上,在传统编程(例如 C++)中,这是鼓励的——你应该重构你的代码,使每个函数都尽可能短:

function baseInfoHandler(error, response) {
    console.log(response);
    const query = { type:'init' , family: name, id: num };

    api.getMainInfo(address, company, query, mainInfoHandler);
}

function mainInfoHandler(error, response) {
    console.log(response);

    api.getBackData(num, backDataHandler);
}

function backDataHandler(error, orders, genre) {
    console.log(orders);
}

function getData() {
    api.getBaseInfo(name, num, baseInfoHandler);
}
getData();

上面的代码扁平化了回调的嵌套。有些人(尤其是老前辈)可能会争辩说,这段代码比使用匿名函数的代码写得更好。但是,进行这种重构也有缺点。

第一个缺点是一个非常小的缺点,基本上可以忽略:有些人就是不喜欢声明额外的函数或变量。使用匿名函数意味着我们不需要考虑函数名。但这只是口味问题,就像我提到的,可以完全忽略。

第二个缺点更为关键。如果您的代码使用闭包,则无法像上面那样展平代码。或者至少如果您依赖闭包,则实施起来会更加困难。因此,我们需要一种嵌套范围(维护闭包)的方法,但仍要避免像我给出的第一个示例那样的深度嵌套。

随之而来的是设计模式。一群才华横溢的程序员开始在各种论坛和博客文章中讨论如何将异步回调封装在一个可以返回的对象中。然后,函数的调用者可以使用所述对象以他们喜欢的任何方式处理异步行为。这种设计模式的名字叫做 Promise,在 javascript 发布 Promise 的默认实现之前,有几个库实现了这种设计模式,通常具有不同的特性,但都以一种方式相互兼容:它们都有一个 .then() 方法,您可以将回调传递给它。

注意:这就是 Promise 设计模式所做的一切。与其编写一个接受回调的函数,不如编写一个返回 Promise 的函数。 Promise 有一个接受回调的.then() 方法。

代码仍然是基于回调的,但现在我们可以通过扁平化回调嵌套来使代码更整洁。首先,我们需要将您的函数转换为 Promise:

function promiseBaseInfo (name,num) {
    return new Promise((resolve, reject) => {
        api.getBaseInfo(name, num, (error, response) => {
            if (error) {
                reject(error);
            }
            else {
                resolve(response);
            }
        });
    });
}

function promiseMainInfo(address, company, query) {
    return new Promise((resolve, reject) => {
        api.getMainInfo(address, company, query, (error, response) => {
            if (error) {
                reject(error);
            }
            else {
                resolve(response);
            }
        });
    });
}

function promiseBackData(num) {
    return new Promise((resolve, reject) => {
        api.getBackData(num, (error, orders, genre) => {
            if (error) {
                reject(error);
            }
            else {
                resolve({
                    orders: orders,
                    genre: genre
                });
            }
        });
    });
}

现在我们有了你的函数的 Promisified 版本,我们可以像这样使用它们:

function getData() {
    promiseBaseInfo(name, num)
        .then(response => {
            console.log(response);

            return promiseMainInfo(address, company, { type:'init' , family: name, id: num });
        })
        .then(response => {
            console.log(response);

            return promiseBackData(num);
        })
        .then(response => {
            console.log(response.orders);
        });
}
getData();

如您所见,.then() 都处于同一嵌套级别。

异步/等待

虽然 Promises 在简化异步代码方面有很大帮助,但它们仍然是基于回调的。此外,执行诸如循环一堆异步任务之类的事情很复杂,因为您需要编写一种递归函数来执行此操作(这使我们回到了回调领域)。

ECMAScript 版本 6 引入了一种新语法来处理 Promise:asyncawait 关键字。

它所做的只是编译这样编写的代码:

async function foo () {
    let x = await bar();
    console.log(x);
}

进入这个:

function foo () {
    return bar().then(x => console.log(x));
}

从解释器的角度来看,Javascript 没有添加任何新内容。这些仍然是 Promise。然而,编译器现在处理如何将您的代码正确地构造成 Promise。

使用async/await,我们可以进一步简化您的代码:

async function getData() {
    let response1 = await promiseBaseInfo(name, num);
    console.log(response1);

    let response2 = await promiseMainInfo(address, company, { type:'init' , family: name, id: num });
    console.log(response2);

    let response3 = await promiseBackData(num);
    console.log(response3.orders);
}
getData();

请注意,Promiseasync/await 都不是使您的代码按照您想要的方式工作的必要条件。而且它们不会改变您的代码的行为 - getData() 函数仍然是异步的(不是同步的)并且会在它记录所有响应之前返回。它只是使代码更易于阅读。然而,在幕后仍然有一个回调被调度并在以后执行,只有async/await 我们让编译器编写回调,我们只是以线性方式编写代码。请务必记住,即使使用 async/await,代码仍然不同步

另请注意,我们仍然无法使用您原来的 api... 函数。我们需要将它们转换为 Promisified 版本。

【讨论】:

  • 非常感谢,这帮助很大。很抱歉没有早点回复,但我的问题已关闭,我没有意识到它已重新打开
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-12
  • 2021-09-15
  • 2019-07-01
  • 2021-05-07
  • 1970-01-01
  • 2021-11-14
  • 2019-02-24
相关资源
最近更新 更多