简体   繁体   中英

Javascript nested asynchronous calls execution order

I'm having trouble understanding how asynchronous code runs in javascript.

I have a code similar to the following:

 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'));

The output of the above code is:

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

As you can see in the output, inner1 was started before outer started, which is very weird! What I expect is that all inner calls start and finish within the start and finish of outer .

I did a lot of research on Google but couldn't find anything helpful unfortunately.

What worked for me is to explicitly emulate wrap function for outer call like below:

 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();

The output of the above code is what I really expect:

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

What am I doing wrong that makes inner1 start before outer is started?

Your question demonstrates a number of misunderstandings about the effective use of async and await -

 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 and await are not special

It's a useful exercise to imagine that async and await do not exist and you have to invent them on your own -

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
]

Note the uppercase Async and Await . These are plain functions of our own making -

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()
  })
}

Hopefully this helps you see what's going on behind the scenes of async and await . It's nothing more than a bit of syntactic sugar to replace a program you could've written by yourself:D

Expand the snippet below to verify the behaviour of our homemade Async and Await below -

 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)

For more info on misuse of async and await , please see this related Q&A

After many attempts, I found that asynchronous functions run synchronously until they reach either await or return.

The behavior that is happening in the first code can be explained as the following:

As in any function call, function parameters are evaluated first and before evaluating the function itself, calling wrap(outer(5000), 'outer') will evaluate the outer(5000) function call first. While evaluating outer call, and especially the first line wrap(inner(ms), 'inner1') , the same thing will happen, inner(ms) will be evaluated first registering the first promise in these nested calls which will be wrapped by wrap function (which is the first call to wrap function), and since wrap(inner(ms), 'inner1') is the first asynchronous call (first await statement) in outer function, then it will be evaluated synchronously, after that all other await statements will register other promises that depend on the first promise await(inner(ms) 'inner1') , then the outer function will be wrapped by wrap . This is why inner1 is started first, outer is started after, then everything else runs as expected.

The solution I found for this is to pass a callback that returns the promise to wrap function instead of passing the promise right away.

 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'));

well from my understanding of asynchronous JS, all the code runs at the same time as opposed to synchronous code.

The only reason why that could be happening is that inner1 execution happens faster than outer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM