Javascript 等待在异步函数中的执行顺序不正确

Javascript Awaits not executing in correct order within Async function

我知道他们有很多关于 async 和 await 以及 promises 的问题,我已经查看了所有这些问题,但我似乎无法让任何解决方案适用于我的具体情况。我确定是因为我对 promises 和回调的使用感到困惑。我只想按顺序触发这 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 个调用同时被触发一样。我不关心使用什么方法,我只想知道如何安排我的代码,以便每次调用都在前一次完成后执行。

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

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

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

第二个缺点更为严重。如果您的代码使用闭包,则像上面那样扁平化代码将不起作用。或者至少,如果您依赖闭包,则实施起来会更加困难。因此,我们需要一种方法来嵌套作用域(保持闭包),但仍要避免像我给出的第一个示例那样的深度嵌套。

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

注意:这就是 Promise 设计模式所做的全部。您不必编写一个接受回调的函数,而是编写一个 return 是 Promise 的函数。 Promise 有一个接受回调的 .then() 方法。

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

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

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

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() 都处于同一嵌套级别。

async/await

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

ECMAScript 版本 6 引入了一种新语法来处理承诺: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() 函数仍然是异步的(不是同步的)并且将 return 之前记录所有响应。它只是使代码更易于阅读。然而,在幕后仍然有一个回调被安排并在以后执行,只有 async/await 我们让编译器编写回调,我们只是以线性风格编写代码。重要的是要记住,即使 async/await 代码仍然 不同步

另请注意,我们仍然 无法使用您原来的api... 功能。我们需要将它们转换成它们的 Promified 版本。