异步等待函数的奇怪行为
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 之前@...))
【问题讨论】:
您的第一个测试使用async
和await
,它们按顺序运行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以上是关于异步等待函数的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章