异步等待函数的奇怪行为

Posted

技术标签:

【中文标题】异步等待函数的奇怪行为【英文标题】:Async await strange behaviour with functions 【发布时间】:2018-07-18 03:00:51 【问题描述】:

我有以下异步代码示例:

// Functions

function getSomePromise() 
    let a = new Promise((resolve, reject) =>     
        setTimeout(function()
            console.log("Inside promise...");
            resolve("Success!"); 
        , 1000);
    );

    return a;


async function someWrapper(i) 
    console.log('A: '+ i);
    await getSomePromise();
    console.log('B: ' + i);    

还有两个测试:

async function test1() 
    for(let i=0; i<5; i++) 
        // body copy-pasted of someWrapper function:
        console.log('A: '+ i);
        await getSomePromise();
        console.log('B: ' + i);
        


async function test2() 
    for(let i=0; i<5; i++) 
        someWrapper(i);                
        

这是运行 separatley test1()test2() 后 chrome 控制台中的结果:

Test 1               |      Test 2
---------------------------------------------
A: 0                 |      A: 0
Inside promise...    |      A: 1
B: 0                 |      A: 2
A: 1                 |      A: 3
Inside promise...    |      A: 4
B: 1                 |      Inside promise...
A: 2                 |      B: 0
Inside promise...    |      Inside promise...
B: 2                 |      B: 1
A: 3                 |      Inside promise...
Inside promise...    |      B: 2
B: 3                 |      Inside promise...
A: 4                 |      B: 3
Inside promise...    |      Inside promise...
B: 4                 |      B: 4

问题:为什么当我们在 for-loop (test2) 中使用函数 someWrapper() 时,我们得到的结果与我们将这个函数体直接复制粘贴到 for-loop (test1) 中的结果不同?

(上面的例子非常抽象,但是在调用 ajax 请求(而不是 console.log('A: '+ i);console.log('B: '+ i);)时“我发现了这种行为”,这在我的应用程序中非常重要(请求 A1 必须在请求 @987654332 之前@...))

【问题讨论】:

您的第一个测试使用asyncawait,它们按顺序运行promise。第二个测试没有,所以每个 Promise 都会开始,然后按顺序完成。 @Sidney 你能详细说明一下吗——在我看来——我在 someWrapper() 和 Test2() 中使用异步/等待,我以同步(通过 for 循环)方式调用这个函数。跨度> 在第一个测试循环中,await 导致循环在 getSomePromise() 函数处暂停,直到 promise 解决。在第二个测试中,没有await,所以javascript 在启动每个promise 后愉快地继续循环。如果不在函数中使用await,则创建函数async 并没有真正做任何事情。 @Sidney 唯一的区别是我将循环体(在测试 1 中)移动到功能(测试2)。在我看来,这种行为是违反直觉的——你同意吗? someWrapper 将立即返回一个解析为未定义的承诺。 await 仅在 someWrapper 函数中“等待”,但调用 someWrapper 的函数将立即收到一个以 undefined 解析的承诺。函数总是返回一些东西,如果你不在代码中,那么它将返回未定义的。如果它是一个没有返回的异步函数,那么它将返回一个以 undefined 解析的承诺。 【参考方案1】:

看cmets

@HMR - 嗯......我不明白 - 有问题的例子有异步 函数 someWrapper() 但该函数不返回任何内容(它 甚至没有返回语句(!)) - 你能解释一下你是什么 指的是async functions immediately return a promise? - 卡米尔 Kielczewski

您似乎不了解异步等待。我通常建议人们在你理解承诺之前解雇等待。但是在下一条评论中,我会给你答案:

someWrapper 将立即返回一个解析为 不明确的。 await 仅在 someWrapper 函数中“等待”,但 调用 someWrapper 的函数将立即收到一个承诺 在未定义中解析。函数总是返回一些东西,如果你不这样做 在代码中,它将返回未定义。如果是异步函数 如果没有返回,那么它将返回一个解决的承诺 未定义 - HMR。

Await 是 Promise 的语法糖(看起来更漂亮的代码),实际上并不等待任何东西。

也许下面的代码可以解决问题:

var test = async () => 
   await 22;//doesn't even matter if value is promise
   console.log("after wait");

var result = test();
console.log("outside test we don't wait for anything",result);

如果您不明白为什么该代码的输出是:

在测试之外,我们不会等待任何东西 Promise

等待之后

那么我建议你只使用 Promise,直到你这样做为止。

【讨论】:

我编辑你的答案并添加我们的 queestion-cmets 提供关键提示 - async function 不是“正常功能”但它总是返回承诺(这是异步和正常之间的区别(非异步) 函数 - 我没有意识到这一点) - 这是我理解为什么问题示例表现得这样的关键点 还有一件事:你说Await is syntax sugar (nicer looking code) for promises and doesn't actually wait for anything. - 'anything' except 承诺“完成”(解决或拒绝) - ? 我对您的 sn-p 也有疑问 - 如果我删除 await 22 行,那么控制台中的第一行将是 after wait(控制台行打印开关的序列)。我不明白 - 为什么在这种情况下,来自var result = test(); 的承诺会立即执行(并将某些内容打印到控制台)? @KamilKiełczewski 它不等待异步函数之外的任何内容。当函数中没有等待时,等待后没有代码排队,因此日志的顺序不同。我再次建议使用 promise 语法而不是 async。刚刚更新了another answer(更新中),这可能会给我一些来自 OO 人的回击,但我认为 promise 对象是一种完美的数据类型,通常只需要 async 和 await ,因为你正在编写一个正在做的函数很多。 也许this 也可以提供帮助。【参考方案2】:

测试2:

你的 test2 不是异步的,直到你让它异步。您已经在 test2 中编写了同步代码。它们是 console.log。只有 test2 中的异步代码调用了 Promise。让我们分解一下

async function test2() 
    for(let i=0; i<5; i++) 
        someWrapper(i);                
        

上面的代码依次触发 someWrapper() 5 次。所以它编写了第一个 sync 代码,即 console.log('A'+i) 5 次在控制台中连续显示。

然后 每个 someWrapper() 等待 async 承诺返回并行。在每个承诺解决后,它会打印“内部承诺”。 直到 promise 解决,执行停止并且不能继续下一步

然后,在解决 promise 后,它会在控制台中打印出第二个 sync 代码,即 console.log('B'+i)

测试1:

test1 的行为与 test2 不同。让我们分解一下

async function test1() 
    for(let i=0; i<5; i++) 
        // body copy-pasted of someWrapper function:
        console.log('A: '+ i);
        await getSomePromise();
        console.log('B: ' + i);
        

主要区别在于你是awaiting inside for loop。所以这实际上将是 pause loop 而不是 test1

所以每次迭代都会打印出console.log('A'+i)

然后暂停 await getSomePromise()

的迭代

当 promise 返回时将打印“Inside Promise”

然后打印console.log('B'+i)

然后继续下一次迭代。

【讨论】:

@AL-zami - 您认为这种 JS 行为是直观的还是违反直觉的? (因为在我看来,在重构期间,我只将 for 循环体移动到单独的函数中,我很惊讶这一移动会改变行为......) @KamilKiełczewski 实际上 javascript 是单线程的。在异步代码中,同步代码将以同步方式运行。 async 不会使同步代码异步。 一件事,那就是,这并不违反直觉。引入了 promise 和 async-await 来消除回调链的混乱。它们有助于以同步方式编写异步代码 @AL-zami 我认为这是反直觉的,因为 OP 和我见过的许多其他问题认为 await 实际上是在等待而不是 async 函数立即返回一个承诺。如果人们在使用异步语法之前学会使用 Promise,那么这里的问题就会少很多。 @HMR 是的!首先,我也遇到了理解它的困难。但是当图片变得清晰时,它似乎不再那么反直觉了。我同意你的观点,学习曲线应该是 Promise > Generators > async-await

以上是关于异步等待函数的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

Dart / Flutter:Isolate ***函数的异步行为

等待/通知的奇怪java行为

异步程序未异步运行的奇怪行为

winsock c++中recv函数的奇怪行为

从 cython c 调用 python 函数时的奇怪行为

猫鼬 findById 的异步/等待行为