How to implement Promise.all?

Recently, I was challenging myself to implement some of the core APIs in Javascript. And Promise.all turns out to be a quite interesting one. If you don't want to get spoiled, try it yourself first!

Table of Contents

What does it do?

Promise.all allow us to resolve values form multiple promises at once. Imagine your website has to fetch 2 different APIs before rendering a specific section. We could do the following:

fetch(siteA).then(res => res.json()).then(res1 => {
    fetch(siteB).then(res => res.json()).then(res2 => {
        // do render with `res1` and `res2`
    });
});

There are two issues with this implementation:

  1. we are kind of in a callback hell
  2. siteA and siteB are fetched one after another, so it slows down the process

We can solve these two problems with Promise.all!

const promises = [fetch(siteA), fetch(siteB)].map(p => p.then(res => res.json()));
Promise.all(promises, ([res1, res2]) => {
    // do render with `res1` and `res2`
});

Now the callback hell is gone and we are no longer fetching siteB after siteA is fetched, we are doing both in parallel! Hence faster!

Implement with counters

This is my very first attempt. I have used a results list to store all the result. And use a counter to check how many promises have resolved so far. If the counter become the number of promises (ps) passed in, resolve the returning promise with results.

Promise.all = (ps) => new Promise((res, rej) => {
    const results = [];
    const counter = 0;
    ps.forEach((p, i) => p.then(result => {
        results[i] = result;
        if (++counter >= p.length) return res(results);
    }).catch(rej));
});

This implementations looks disgusting (in my opinion), but it works. Let's try to get a more elegant solution. Recursion always comes to my mind when we talk about elegance. Let's try recursion?

Recursion

Recursion seems much more easier. We just have to take the leap of faith, assuming Promise.all works -> get the value from the first promise (result) -> use Promise.all on the rest of the promises (results) -> combining result and results -> DONE!

Promise.all = (ps) => {
    if(ps.length <= 0) return Promise.resolve([]);

    const [head, ...tail] = ps;
    return head.then(result => {
        return Promise.all(tail).then(results => [result, ...results])
    });
};

Hmm, turns out this is not as elegant as I thought it could be. I am sure we could even do better with async/await!

async/await

Promise.all = async (ps) => {
    const results = [];
    for (const p of ps) {
        results.push(await p);
    }
    return results;
};

HOW AMAZING IS ASYNC/AWAIT! I absolutely love it! So how does a recursive solution look like?

Promise.all = async (ps) => {
    if (ps.length <= 0) return [];
    const [head, ...tail] = ps;
    return [(await head), ...(await Promise.all(tail))];
};

Awesome! This async/await recursive approach is quite minimal.

Hmmm, it is quite fun to implement core API from scratch. Sometimes we take those things for granted, it is important to know the method behind the scene in order to become a better programmer!

Tell me what you think! Talk to you next time!