Skip to content

Instantly share code, notes, and snippets.

@indiesquidge
Last active January 20, 2024 15:03
  • Star 33 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save indiesquidge/5960274889e17102b5130e8bd2ce9002 to your computer and use it in GitHub Desktop.
Recreating Promise.all with async/await
/*
Let us re-create `Promise.all`
`Promise.all` method returns a promise that resolves when all of the promises in the iterable argument have resolved,
or rejects with the reason of the first passed promise that rejects.
Read more about `Promise.all` on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
A basic example would be something like this:
Promise.all([promise1, promise2, promise3])
.then(allResolvedData => console.log('here are the resolutions to the promises', allResolvedData))
*/
let allWithAsync = (...listOfPromises) => {
return new Promise(async resolve => {
let results = []
for (let promise of listOfPromises) {
results.push(await promise.then(async resolvedData => await resolvedData))
if (results.length === listOfPromises.length) resolve(results)
}
})
}
const promise1 = Promise.resolve('first')
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 1000, 'second'))
const promise3 = Promise.resolve('third')
allWithAsync(promise1, promise2, promise3)
.then(resolvedData => {
console.log(resolvedData) // ['first', 'second', 'third']
})
/*
This is good, and it returns our promise resolutions in the right order,
but what happens if our list includes something that is not a promise?
*/
const promise4 = 'promise4'
/*
This will break our current `allWithAsync` by telling us that `promise.then` is not a function for our new `promise4`.
We can fix this with a small change to our function that wraps all items in the `listOfPromises` in a `Promise.resolve`.
Doing this will return each item as a promise that is resolved with said item.
*/
allWithAsync = (...listOfPromises) => {
return new Promise(async resolve => {
let results = []
for (let promise of listOfPromises.map(Promise.resolve, Promise)) {
results.push(await promise.then(async resolvedData => await resolvedData))
if (results.length === listOfPromises.length) resolve(results)
}
})
}
allWithAsync(promise1, promise2, promise3, promise4)
.then(resolvedData => {
console.log(resolvedData) // ['first', 'second', 'third', 'fourth']
})
/*
This is looking pretty good. One last thing: rejection handling.
*/
const promise5 = Promise.reject('rejected!')
/*
As mentioned above, `Promise.all` will either return a promise that resolves when all of the promises in
the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.
Let's handle the rejection case.
So far, our `Promise.then` has only been dealing with the fulfillment scenario of each promise.
A quick look at the API shows us that `Promise.then` can take two arguments: a fulfillment and a rejection handler.
Notice that we are returning a new Promise from our function. `new Promise` takes a callback with two arguments.
We are using one right now: `resolve`. But the second argument is `reject`.
If we include this in our promise instantiation callback and call it in our `Promise.then` function,
we should get the error handling we are looking for!
*/
allWithAsync = (...listOfPromises) => {
return new Promise(async (resolve, reject) => {
let results = []
for (let promise of listOfPromises.map(Promise.resolve, Promise)) {
results.push(await promise.then(async resolvedData => await resolvedData, reject))
if (results.length === listOfPromises.length) resolve(results)
}
})
}
allWithAsync(promise1, promise2, promise3, promise4, promise5)
.then(resolvedData => {
console.log(resolvedData) // will not run since we have a rejection!
}, rejectionReason => console.log('reason:', rejectionReason)) // reason: rejected!
@graup
Copy link

graup commented Oct 25, 2017

Doesn't this process all the responses serially? You lose the advantage of parallel processing with this, don't you?

Why not just use

async function load(...) { }
await Promise.all([load(1), load(2), ...]);

@heisian
Copy link

heisian commented Nov 6, 2017

Yeah your script doesn't do anything except re-create default behaviour...

(async () => {
  const promise1 = new Promise(resolve => {
    setTimeout(() => { resolve() }, 5000)
  })

  const promise2 = new Promise(resolve => {
    setTimeout(() => { resolve() }, 10000)
  })

  const promise3 = new Promise(resolve => {
    setTimeout(() => { resolve() }, 2000)
  })

  const promises = [promise1, promise2, promise3]

  await Promise.all(promises) // This already works as expected

  console.log('This will output 10000 ms after the previous line was called.')
})()

@cikasfm
Copy link

cikasfm commented Jan 24, 2018

Sometimes developers just "develop" something to make "development" "easier"...

@nabilfreeman
Copy link

nabilfreeman commented Mar 8, 2018

This is exactly what Promise.all does. I don't think your code offers any additional functionality except for passing in arguments instead of an array.

@cstroliadavis
Copy link

cstroliadavis commented Jun 23, 2018

Here's an equivalent of writing Promise.all with async/await, without using Promise.all

async function doOneSecond(){
  const p = await new Promise(resolve => setTimeout(resolve, 1000)).then(()=>'one');
  console.log('Completed First');
  return p;
}

async function doTwoSeconds(){
  const p = await new Promise(resolve => setTimeout(resolve, 2000)).then(()=>'two');
  console.log('Completed Second');
  return p;
}

async function doIt(){
   // key is to set return value (the Promise) of our async functions to a constant or variable
    const p2 = doTwoSeconds(); // no await here, so not waiting to start
    const p1 = doOneSecond(); // this one

    // then await the value result of those
    // This is essentially our Promise.all here:
    let m2 = await p2 // wait for both to complete
    let m1 = await p1 // order should not matter, just time to complete

    console.log(`"${m1}" and "${m2}" are complete. Neither blocked the other`);

    // Alternatively, if you really wanted it to look and behave a bit more like a classic Promise.all, you could
    // write it something like this:
    /*
    let [m2, m1] = [await p2, await p1] // deconstructor is optional, but would basically let you reduce the lines above to a single line
    console.log(`"${m1}" and "${m2}" are complete. Neither blocked the other`);
    */
}

doIt();

@francophongvu
Copy link

Hi, cstroliadavis
Did you test your code? I followed it, but it doesn't work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment