【问题标题】: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 解析的承诺。

标签: javascript function asynchronous es6-promise


【解决方案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
猜你喜欢
  • 1970-01-01
  • 2017-09-13
  • 2021-11-14
  • 1970-01-01
  • 2023-02-21
  • 2022-01-08
  • 1970-01-01
  • 2021-11-18
  • 2018-09-21
相关资源
最近更新 更多