0

I have a builder for long running async operations, and I need every one of those operation to run blocking, there should be only single task running at any moment.

In the code example, I want test to wait until all of it's internal awaits are resolved and only then resolve test function itself, allowing the code to continue with the next task.

;(async () => {

  const after = (time) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
      }, time)
    })
  }

  const test = async (item) => {
    console.log("running item", item)
    await after(100)
    console.log("running step 1 item", item)
    await after(100)
    console.log("running step 2 item", item)
    await after(100)
    console.log("running step 3 item", item)
    await after(100)
    console.log("running step 4 item", item)
  }
  
  console.log("running")
  const promises = [1,2,3,4,5].map((item) => {
    return () => {
      test(item)
    }
  })
  for (const promise of promises) {
    console.log('running promise', promise)
    await promise()
  }

})()

At the moment, this code gives me an exception UnhandledPromiseRejectionWarning: TypeError: promise is not a function and also runs all test functions kind of in parallel, when every await call allows another test task to be run. Which is according to specifications, but not what I need.

Here is the output at the moment, showing that execution iterates over items, while I want item 1 to be fully processed before item 2

running
running item 1
running item 2
running item 3
running item 4
running item 5
running step 1 item 1
running step 1 item 2
running step 1 item 3
running step 1 item 4
running step 1 item 5
running step 2 item 1
running step 2 item 2
running step 2 item 3
running step 2 item 4
running step 2 item 5
running step 3 item 1
running step 3 item 2
running step 3 item 3
running step 3 item 4
running step 3 item 5
running step 4 item 1
running step 4 item 2
running step 4 item 3
running step 4 item 4
running step 4 item 5

SOLUTION: The key is not to create all promises but rather wait for each one to finish before creating the next one. Code, that works:

;(async () => {
  const after = (time) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
      }, time)
    })
  }

  const test = async (item) => {
    console.log("running item", item)
    await after(100)
    console.log("running step 1 item", item)
    await after(100)
    console.log("running step 2 item", item)
    await after(100)
    console.log("running step 3 item", item)
    await after(100)
    console.log("running step 4 item", item)
  }

  console.log("running")
  const promises = [1, 2, 3, 4, 5].map((item) => {
    return async () => {
      await test(item)
    }
  })
  for (const promise of promises) {
    await promise()
  }
})()
AAverin
  • 3,014
  • 3
  • 27
  • 32
  • You should `await promise` not `await promise()` – Evert Aug 17 '20 at 06:27
  • @Evert good point, I adjusted the code to use functions. This doesn't solve the parallel execution though – AAverin Aug 17 '20 at 06:28
  • Does this answer your question? [Call async/await functions in parallel](https://stackoverflow.com/questions/35612428/call-async-await-functions-in-parallel) – VLAZ Aug 17 '20 at 06:30
  • @VLAZ interesting idea. This will force me then to write `test` function as a list of promises instead of it being a normal async function. And running tests as `Promise.all` will put them in parallel, while I need them to be sequential – AAverin Aug 17 '20 at 06:32
  • This is probably more what you're after: [Resolve promises one after another (i.e. in sequence)?](https://stackoverflow.com/q/24586110) – VLAZ Aug 17 '20 at 06:34
  • @VLAZ I am doing this with my for loop. It could be re-written as a reduce that would chain promises, but this doesn't affect the result – AAverin Aug 17 '20 at 06:37
  • @AAverin you're awaiting the *executed promises* in a loop. Once you have a promise, it's active, so `await`-ing it will not change the order of resolution. You need to execute a function, get a promise, await it, *then* execute the next function, get a promise and execute it. – VLAZ Aug 17 '20 at 06:40

1 Answers1

0

Currently you're constructing all promises in one go. You'll want to only construct one promise when the previous one is done:

for (let i = 1; i <= 5; i++) {
    await test(i);
}

Alternatively you can construct a promise chain:

const p = [1, 2, 3, 4, 5].reduce((p, i) => p.then(() => test(i)), Promise.resolve())
await p;

This is equivalent to:

const p = test(1).then(() => test(2)).then(...);
deceze
  • 510,633
  • 85
  • 743
  • 889
  • I have tried constructing a chain with ```const promises = [1, 2, 3, 4, 5].map((item) => { return test(item) }) promises.reduce((p, fn) => { return p.then(fn) }, Promise.resolve())```, it didn't affect the result – AAverin Aug 17 '20 at 06:39
  • 1
    @AAverin don't reduce the *already executed and active* promises. The answer shows exactly how to do it - get one promise first, *then* chain on the next one. Once you have an array of promises, it's too late to ensure the proper execution. Imagine you send three people to fetch you coffee from different places. Now while they are absent, you want to make sure they come back in particular order. Instead you should be dispatching them one at a time only when the previous person returns. – VLAZ Aug 17 '20 at 06:42
  • The idea to have delayed promises construction worked. I will update with correct code. Thanks – AAverin Aug 17 '20 at 06:43