【问题标题】:Javascript nested asynchronous calls execution orderJavascript嵌套异步调用执行顺序
【发布时间】:2021-08-11 01:20:25
【问题描述】:

我无法理解异步代码如何在 javascript 中运行。

我有一个类似下面的代码:

const start = name => console.log(`${name} started`);
const finish = name => console.log(`${name} finished`);
const wrap = async (promise, name) => {
    start(name);
    const promiseResult = await promise;
    finish(name);
    return promiseResult;
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const inner = async ms => {
    return await sleep(1000);
}
const outer = async ms => {
    await wrap(inner(ms), 'inner1');
    await wrap(inner(ms), 'inner2');
    await wrap(inner(ms), 'inner3');
}
const testPromise = async promise => {
    const t0 = performance.now();
    const promiseResult = await promise;
    const t1 = performance.now();
    console.log(`Running promise took ${t1 - t0} milliseconds`);
    return promiseResult;
}
testPromise(wrap(outer(5000), 'outer'));

以上代码的输出为:

inner1 started
outer started
inner1 finished
inner2 started
inner2 finished
inner3 started
inner3 finished
outer finished
Running promise took 3026.2199999997392 milliseconds

正如你在输出中看到的,inner1 是在outer 启动之前启动的,这很奇怪!我期望的是所有inner 调用都在outer 的开始和结束内开始和结束。

我在 Google 上做了很多研究,但遗憾的是找不到任何有用的东西。

对我有用的是为outer 调用显式模拟wrap 函数,如下所示:

const start = name => console.log(`${name} started`);
const finish = name => console.log(`${name} finished`);
const wrap = async (promise, name) => {
    start(name);
    const promiseResult = await promise;
    finish(name);
    return promiseResult;
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const inner = async ms => {
    return await sleep(1000);
}
const outer = async ms => {
    await wrap(inner(ms), 'inner1');
    await wrap(inner(ms), 'inner2');
    await wrap(inner(ms), 'inner3');
}
const testPromise = async () => {
    const t0 = performance.now();
    const outerName = 'outer';                // -- emulate calling `await wrap(outer(5000), 'outer')`
    start(outerName);                         // --           
    const promiseResult = await outer(5000);  // --
    finish(outerName);                        // -- finished emulation of `wrap`
    const t1 = performance.now();
    console.log(`Running promise took ${t1 - t0} milliseconds`);
    return promiseResult;
}
testPromise();

上面代码的输出是我真正期望的:

outer started
inner1 started
inner1 finished
inner2 started
inner2 finished
inner3 started
inner3 finished
outer finished
Running promise took 3155.5249999510124 milliseconds

我做错了什么导致inner1outer 启动之前启动?

【问题讨论】:

  • 当您拥有a(b()) 时,必须先解析函数调用b(),然后才能处理a()。毕竟,您需要将来自b() 的值传递给a()testPromise(wrap(outer(5000), 'outer')); 也会发生同样的事情 - outerr(5000) 必须被称为 first。进而调用inner。哪个记录它。之后会发生一堆暂停和取消暂停,这就是为什么你会得到outer next。
  • 你运行的第一件事是外部的,它立即返回一个承诺。然后你运行 inner1 ,它也返回一个承诺,并在 1000 毫秒后解决。这将为inner1运行wrap,所以第一个输出是inner1,因为outer需要5000ms才能到达它的wrap调用
  • const inner = async ms => { return await sleep(1000); } 是一种反模式。 const inner = () => sleep(1000) 做了完全相同的事情。在outer 你写await wrap(inner(ms), 'inner1');ms 参数被忽略。也许你的意思是写await wrap(sleep(ms), "first")wrap 返回一个结果,但你没有将它分配给任何东西?

标签: javascript async-await promise


【解决方案1】:

根据我对异步 JS 的理解,所有代码同时运行,而不是同步代码。

可能发生这种情况的唯一原因是内部 1 执行速度比外部执行速度快。

【讨论】:

  • 异步并不意味着并发。 JS 中的异步执行仍然是顺序的。除非有例外,但这不是其中的任何一个。
  • 好的,明白了。感谢您的指正。我对此还有些年轻。
【解决方案2】:

经过多次尝试,我发现异步函数是同步运行的,直到它们到达 await 或 return。

第一个代码中发生的行为可以解释如下:

与任何函数调用一样,首先评估函数参数,然后再评估函数本身,调用wrap(outer(5000), 'outer') 将首先评估outer(5000) 函数调用。在评估outer 调用时,尤其是第一行wrap(inner(ms), 'inner1'),同样的事情会发生,inner(ms) 将首先在这些嵌套调用中注册第一个承诺,这将由wrap 函数包装(这是第一次调用wrap函数),并且由于wrap(inner(ms), 'inner1')outer函数中的第一个异步调用(第一个await语句),那么它将被同步评估,之后所有其他await语句将注册其他依赖于第一个承诺await(inner(ms) 'inner1') 的承诺,那么outer 函数将被wrap 包装。这就是为什么首先启动inner1,然后启动outer,然后其他一切都按预期运行。

我为此找到的解决方案是传递一个回调,将承诺返回给wrap 函数,而不是立即传递承诺。

const start = name => console.log(`${name} started`);
const finish = name => console.log(`${name} finished`);
const wrap = async (promiseCallback, name) => {
    start(name);
    const promiseResult = await promiseCallback();
    finish(name);
    return promiseResult;
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const inner = async ms => {
    return await sleep(1000);
}
const outer = async ms => {
    await wrap(() => inner(ms), 'inner1');
    await wrap(() => inner(ms), 'inner2');
    await wrap(() => inner(ms), 'inner3');
}
const testPromise = async promiseCallback => {
    const t0 = performance.now();
    const promiseResult = await promiseCallback();
    const t1 = performance.now();
    console.log(`Running promise took ${t1 - t0} milliseconds`);
    return promiseResult;
}
testPromise(() => wrap(() => outer(5000), 'outer'));

【讨论】:

  • "我发现异步函数会同步运行,直到在其中第一次 await 调用之后。" 非常小的更正:异步函数同步运行直到它们到达 await 或 @ 987654340@。就在这时,他们立即停了下来。当到达await 时,JS 将开始等待它后面的值(它是一元运算符)解析。如果您使用带有非承诺的await(例如await 4),它的行为就像await Promise.resolve(4)
  • 感谢您的更正和帮助@VLAZ 我编辑了我的答案
【解决方案3】:

您的问题表明了对有效使用asyncawait 的一些误解-

const sleep = ms =>
  new Promise(r => setTimeout(r, ms))

async function wrap (p, label) {
  console.log("started", label)
  const t = Date.now()
  const result = await p
  console.log("finished", label)
  return { result, delta: Date.now() - t }
}

async function inner () {
  await sleep(1000)
  return Math.floor(Math.random() * 100)
}

async function outer () {
  const a = await wrap(inner(), "inner1")
  const b = await wrap(inner(), "inner2")
  const c = await wrap(inner(), "inner3")
  return [a, b, c]
}

wrap(outer(), "outer")
  .then(JSON.stringify)
  .then(console.log, console.error)
started inner1
started outer
finished inner1
started inner2
finished inner2
started inner3
finished inner3
finished outer
{"result":[{"result":58,"delta":1004},{"result":58,"delta":1001},{"result":67,"delta":1000}],"delta":3009}

async 和 await 并不特殊

想象asyncawait 不存在是一个有用的练习,你必须自己发明它们 -

const sleep = ms =>
  new Promise(r => setTimeout(r, ms))

function* pick1 () {
  yield Await(sleep(1000))
  return Math.random()
}
  
function* pick3 () {
  const a = yield Await(pick1())
  console.log("first", a)

  const b = yield Await(pick1())
  console.log("second", b)

  const c = yield Await(pick1())
  console.log("third", c)

  return [a, b, c]
}

Async(pick3()).then(console.log, console.error)
first 0.22559836642959197
second 0.41608184867397835
third 0.3789851899519072
[
  0.22559836642959197,
  0.41608184867397835,
  0.3789851899519072
]

注意大写的AsyncAwait。这些都是我们自己制作的简单功能 -

const isGenerator = x =>
  x?.constructor == (function*(){}()).constructor

const Await = x =>
  isGenerator(x) ? Async(x) : Promise.resolve(x)

function Async (it) {
  return new Promise((resolve, reject) => {
    function next (x) {
      const {value, done} = it.next(x)
      return done
        ? resolve(value)
        : value.then(next, reject)
    }
    next()
  })
}

希望这可以帮助您了解asyncawait 的幕后情况。替换您可以自己编写的程序只不过是一点语法糖:D

展开下面的sn-p来验证下面我们自制的AsyncAwait的行为——

const isGenerator = x =>
  x?.constructor == (function*(){}()).constructor

const Await = x =>
  isGenerator(x) ? Async(x) : Promise.resolve(x)

function Async (it) {
  return new Promise((resolve, reject) => {
    function next (x) {
      const {value, done} = it.next(x)
      return done
        ? resolve(value)
        : value.then(next, reject)
    }
    next()
  })
}

const sleep = ms =>
  new Promise(r => setTimeout(r, ms))

function* pick1 () {
  yield Await(sleep(1000))
  return Math.random()
}
  
function* pick3 () {
  const a = yield Await(pick1())
  console.log("first", a)
  const b = yield Await(pick1())
  console.log("second", b)
  const c = yield Await(pick1())
  console.log("third", c)
  return [a, b, c]
}

Async(pick3()).then(console.log, console.error)

有关滥用asyncawait 的更多信息,请参阅this related Q&A

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多