当前位置: 首页 > 知识库问答 >
问题:

为什么我需要等待一个异步函数,而它不应该返回一个promise?

宁卓
2023-03-14

考虑以下代码:

async function load() {
  const data = await new Promise(resolve => {
    setTimeout(() => resolve([1, 2, 3]), 10);
  }).then(data => data.map(i => i * 10));
  console.log(`Data inside the function: ${JSON.stringify(data)}`);
  return data;
}

function main() {
  const data = load();
  console.log(`Loaded data: ${JSON.stringify(data)}`);
}

main();

这是我得到的输出:

Loaded data: {}
Data inside the function: [10,20,30]

但是如果我把代码改成这样:

async function load() {
  const data = await new Promise(resolve => {
    setTimeout(() => resolve([1, 2, 3]), 10);
  }).then(data => data.map(i => i * 10));
  console.log(`Data inside the function: ${JSON.stringify(data)}`);
  return data;
}

async function main() {
  const data = await load();
  console.log(`Loaded data: ${JSON.stringify(data)}`);
}

main();

我会得到这个:

Data inside the function: [10,20,30]
Loaded data: [10,20,30]

我很困惑,因为根据文档,wait应该暂停执行,直到promise得到解决。在这种情况下,第一个示例应该以数组的形式返回数据。但正如你所看到的,它正在返回一个promise,我不知道为什么!?

同时,文档中有这一部分,我不明白它在说什么:

等待可以分割执行流,允许等待函数的调用方在等待函数的延迟延续之前恢复执行。在等待延迟其函数的继续后,如果这是该函数执行的第一个等待,则立即执行也通过向函数的调用方返回等待函数完成的未决promise并恢复该调用方的执行来继续。

在我看来,只有当代码中的所有函数都是async时,wait才起作用,这很可笑,因为如果我使用的是另一个模块中的函数,我怎么知道它是不是async!?或者我应该谨慎一点,总是用await调用所有函数,不管它们是否是async!!!

[更新]

感谢大家的参与,并为我提供了见解。但我仍然不知道应该如何使用awaitasync。我是否应该始终使用wait调用所有函数?

假设我正在编写一个由多个文件中的多个函数组成的代码。如果我最终使用的库返回promise,或者它是异步函数,那么我是否应该将所有函数调用从异步点追溯到应用程序的入口点,并在进行异步后的所有函数调用之前添加一个等待?或者我应该养成用await调用所有函数的习惯,不管它们是否是async


共有3个答案

梁丘璞瑜
2023-03-14

因为第一个函数是异步的,所以它是在执行main函数的其余部分时运行的,当结果记录到下一行时,这是没有帮助的。在使用结果之前,您必须等待函数完成执行,因此可以使用async/wait,如示例所示:

async function main() {
  const data = await load();
  console.log(`Loaded data: ${JSON.stringify(data)}`);
}

或者使用。然后

function main() {
  load().then(data => {
    console.log(`Loaded data: ${JSON.stringify(data)}`);
  });
}

这里的提示是:如果函数是async,您必须错误地使用它async,因为它总是返回一个Promise。

苗森
2023-03-14

async/wait只是语法糖,这意味着,它们不会给语言带来任何新功能,只是有用的promise包装器。

如果函数标记为async,它总是返回一个promise:

> async function f() { return 42; }
undefined
> f()
Promise { 42 }

此外,如果一个函数是async,您可以wait为它里面的任何promise(包括另一个async函数的结果),并且函数代码的执行将暂停在wait,直到promise被解决或拒绝。

回答您的问题:如果使用库函数,您通常知道它是否返回promise(如果它标记为async,它肯定会返回)。因此,请确保等待它或使用。然后使用返回的promise。

吕飞翼
2023-03-14

所有async函数都返回一个promise。都是。

这一promise最终将用您从异步函数返回的任何值来解决。

wait仅阻止执行内部的async函数。它不会阻止函数之外的任何内容。从概念上讲,异步函数开始执行,一旦它命中waiit指令,它立即从函数返回一个未实现的promise,外部执行世界得到这个promise并继续执行。

稍后,waited中的内部promise将得到解决,然后继续执行函数的其余内部。最终,函数的内部将完成并返回一个值。这将触发解析从具有该返回值的函数返回的promise。

仅供参考,在你的load()函数中有很多多余的东西。你可以从这里改变它:

async function load() {
  const data = await new Promise(resolve => {
    setTimeout(() => resolve([1, 2, 3]), 10);
  }).then(data => data.map(i => i * 10));
  console.log(`Data inside the function: ${JSON.stringify(data)}`);
  return data;
}

对此:

function load() {
    return new Promise(resolve => {
        setTimeout(() => resolve([1, 2, 3]), 10);
    }).then(data => data.map(i => i * 10));
}

然后,像这样使用它:

load().then(result => {
    console.log(result);
});

或者,我更喜欢将promise的手动创建封装在自己的函数中,如下所示:

function delay(t, v) {
    return new Promise(resolve => {
        setTimeout(resolve.bind(null, v), t);
    });
}

function load() {
    return delay(10, [1, 2, 3]).then(data => data.map(i => i * 10));
}

事实证明,这个小小的delay()函数通常在许多需要延迟promise链的地方都很有用。

感谢大家的参与,并为我提供了见解。但我仍然不知道应该如何使用wait和async。

首先,如果需要在函数内部使用wait,则大多数情况下只标记函数async

其次,当您有多个异步操作并且希望对它们进行排序时,您通常使用await(在async函数中),这通常是因为第一个操作提供的结果用作第二个操作的输入。当您只有一个异步操作时,可以使用await,但与简单的.then()相比,它并没有多大优势。

下面是一些使用async/wait的好理由:

对多个异步操作进行排序

假设您有所有异步的getFromDatabase()getTheUrl()getTheContent()。如果有任何失败,您只需要用第一个错误拒绝返回的promise。

以下是没有异步/等待时的情况:

function run() {
    return getFromDatabase(someArg).then(key => {
        return getTheURL(key);
    }).then(url => {
        return getTheContent(url);
    }).then(content => {
         // some final processing
         return finalValue;
    });
}

下面是使用async/await时的情况:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        
}

在这两种情况下,函数都返回一个与finalValue解析的promise,以便调用者使用相同的两种实现:

run(someArg).then(finalValue => {
    console.log(finalValue);
}).catch(err => {
    console.log(err);
});

但是,您会注意到async/wait实现更像是序列化的同步外观,看起来更像非异步代码。许多人发现这更容易写,更容易阅读,更容易维护。步骤之间的处理越多,包括分支,async/wait版本的优势就越大。

自动捕获被拒绝的promise和同步异常

正如我前面所说,async函数总是返回一个promise。他们还必须进行内置的错误处理,自动将错误传播回返回的promise。

不言而喻,如果您手动从async函数返回一个promise,并且该promise拒绝,那么从async函数返回的promise将拒绝。

但是,如果您使用的是await和任何等待拒绝的promise,并且promise上没有.catch(),并且周围没有try/catch,则函数返回的promise将自动拒绝。那么,回到我们前面的例子:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        
}

如果正在等待的三个promise中的任何一个被拒绝,那么函数将短路(停止在函数中执行任何更多的代码)并拒绝异步返回的promise。所以,您可以免费获得这种形式的错误处理。

最后,一个async函数也为您捕获同步异常,并将它们转换为拒绝的promise。

在返回promise的正常函数中,如前面所述:

function run() {
    return getFromDatabase(someArg).then(key => {
        return getTheURL(key);
    }).then(url => {
        return getTheContent(url);
    }).then(content => {
         // some final processing
         return finalValue;
    });
}

如果getFromDatabase()引发同步异常(可能是因为someArg无效而触发),则整个函数run()将同步引发。这意味着,调用方要捕获run()中所有可能的错误,必须用try/catch来捕获同步异常,并使用.catch()来捕获被拒绝的promise:

try {
    run(someArg).then(finalValue => {
        console.log(finalValue);
    }).catch(err => {
        console.log(err);
    });
} catch(e) {
    console.log(err);
}

这很混乱,而且有点重复。但是,当run()声明为async时,它将永远不会同步抛出,因为任何同步异常都会自动转换为被拒绝的promise,因此您可以确保以这种方式写入时捕获了所有可能的错误:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        
}

// will catch all possible errors from run()
run(someArg).then(finalValue => {
    console.log(finalValue);
}).catch(err => {
    console.log(err);
});

我应该总是用等待来调用所有函数吗?

首先,您只会在返回promise的函数中使用wait,因为如果函数不返回promise,wait就没有用了(如果不需要,只会增加代码的混乱)。

其次,您是否使用await取决于调用函数的上下文(因为您必须使用async函数才能使用await),取决于逻辑流以及使用await是否有益。

没有意义使用的地方等待

async function getKey(someArg) {
    let key = await getFromDatabase(someArg);
    return key;
}

这里的等待没有做任何有用的事情。您没有对多个异步操作进行排序,也没有对返回值进行任何处理。您只需直接返回promise即可完成完全相同的代码:

async function getKey(someArg) {
    return getFromDatabase(someArg);
}

而且,如果您知道getFromDatabase()从不同步抛出,您甚至可以从声明中删除async

function getKey(someArg) {
    return getFromDatabase(someArg);
}

假设我正在编写一个由多个文件中的多个函数组成的代码。如果我最终使用了一个返回promise的库,或者它是一个异步函数,那么我是否应该将所有函数调用从异步点追溯到应用程序的入口点,并在使它们异步之后在所有函数调用之前添加一个wait?

这是一个过于笼统的问题,在一般情况下很难回答。以下是关于这一总体方向的一些想法:

>

  • 一旦您试图从函数A()返回的结果的任何部分是异步的,或者使用任何异步操作来获得,函数本身就是异步的。在纯Javascript中,您永远不能同步返回异步结果,因此您的函数必须使用异步方法来返回结果(promise、回调、事件等)。

    任何函数B()调用异步函数A(),并且试图根据从A()获得的结果返回结果,现在也是异步的,并且必须使用异步机制。对于调用B()并需要将其结果返回给调用方的函数C()来说,情况就是如此。所以,你可以说异步行为是有传染性的。在调用链中达到不再需要返回结果的某个点之前,一切都必须使用异步机制来传达结果、错误和完成。

    无需特别标记函数async,除非您特别需要async函数的一个优点,例如在该函数内使用wait或它提供的自动错误处理功能。不必在函数声明中使用async,就可以编写返回promise的函数。所以,“不”我不会返回调用链,使所有的都是异步的。我只在有特定原因的情况下使函数异步。通常原因是我想在函数中使用wait,但也有自动捕获同步异常的功能,这些异常会转化为我前面描述的promise拒绝。对于行为良好的代码,通常不需要这样做,但对于行为不良的代码或行为未定义的代码,这样做有时很有用。

    wait也仅在有特定原因时使用。我不只是在每个返回promise的函数上自动使用它。我已经描述了使用它的上述原因。仍然可以使用.then()处理返回promise的单个函数调用的结果。在某些情况下,使用.then()wait只是个人风格的问题,没有特别的原因。

    或者我应该养成习惯,用wait调用所有函数,而不管它们是否异步?

    绝对不是!首先,您最不希望做的事情是采用完全同步的代码,不必要地使其异步,甚至使其看起来异步。异步代码(即使是asyncawait)的编写、调试、理解和维护都比同步代码复杂,因此您永远不会希望通过在其中添加async/await将同步代码不必要地转换为异步代码:

    例如,你永远不会这样做:

    async function random(min, max) {
        let r = await Math.random();
        return Math.floor((r * (max - min)) + min);
    }
    

    首先,这是一个完全同步的操作,可以这样编码:

    function random(min, max) {
        let r = Math.random();
        return Math.floor((r * (max - min)) + min);
    }
    

    其次,第一个async实现使得函数很难使用,因为它现在有一个异步结果:

     random(1,10).then(r => {
         console.log(r);
     });
    

    而不仅仅是简单的同步使用:

     console.log(random(1,10));
    

  •  类似资料:
    • 我有一个异步函数,它调用其中的另一个异步函数。此异步函数正在返回" 你能帮我解决这个问题吗? 以下是第一个异步函数: 如你所见,我调用updateAllProducts函数,并将该值存储到一个变量调用newAllProducts. updateAllProducts是另一个异步函数。 以下是updateAllProducts的代码: 此updateAllProducts函数正在调用另一个异步函数调

    • 问题内容: 我的代码: 当我尝试运行这样的东西时: 我越来越: 但为什么? 我的主要目标是将令牌(从令牌中返回承诺)转换为变量。然后才执行一些操作。 问题答案: 只要其结果尚未解决,promise将始终记录未决。无论promise状态如何(已解决或仍处于待处理状态),您都必须调用promise来捕获结果: 这是为什么? 承诺只是向前的方向;您只能解决一次。a的解析值传递给其或方法。 根据Promi

    • 我试图利用es7异步功能,即。 在这里,所有promise*函数都进行ajax调用,并返回或如果ajax响应满足传递的参数,我相信我不能连续使用3个等待,因此需要一种方法来等待所有这些调用以某种方式返回它们的值。

    • 问题内容: 我的Python程序中有一个保存函数,如下所示: 在此,n为“ 1”。 我收到如下错误: 在外壳中执行相同的加载后,我没有收到任何错误: 为什么会有问题? 问题答案: 您可能从os模块导入了星号: 因此您使用了错误的打开功能。(我想您可以简单地完成,但是可能性较小。)通常,应避免这种导入样式,在实际情况下应避免使用。

    • 我的JavaScript代码如下所示: 完成所有这些异步调用后,我想计算所有数组的最小值。 我怎么能等到他们所有人呢? 我现在唯一的想法是有一个布尔数组叫做done,并在第i个回调函数中将done[i]设置为true,然后说while(not all are done){} edit:我想一个可能的,但很难看的解决方案是在每个回调中编辑done数组,然后在每个回调中设置了所有其他done的情况下调

    • 我正在使用节点8.x。因此,我可以访问所有最新的特性,如异步/等待等。 该场景类似于以下内容(语法不正确,仅供解释): 基本上,User对象的创建依赖于address对象的创建。我希望createUser函数异步执行,即尽快返回一个promise而不等待address对象的创建。 这个问题的目的不是完成任务,而是了解在异步编程中解决这类问题的最佳方法是什么。 我能想到的方法不多:1:创建一个新的p