skip to Main Content

I’m working with promises in JavaScript. I need to call a promise that returns a number 3 times. These conditions must be met:

  • The promises must be run in parallel, not one after the other.
  • If all the promises resolve in less than 50ms, then return the sum of all 3.
  • If any of the promises take over 50ms, then return the sum of the first 2, regardless of how long they take.

I’ve made a start but I don’t know how to return early if the promises take too long:

function getPromise(): Promise<number> {
  return new Promise(resolve => {
    setTimeout(()=>{
      resolve(Math.round(Math.random() * 10))
    }, Math.random() * 100);
  })
};

const TIMEOUT_MS = 50;

async function foo(){
  const promises = [getPromise(), getPromise(), getPromise()];
  const timeBefore = new Date().getTime()
  const results: number[] = await Promise.all(promises);
  const timeAfter = new Date().getTime();
  const duration = timeAfter - timeBefore;

  // if the duration is within 50ms then return the sum of all 3
  if(duration <= TIMEOUT_MS) {
    const sum: number = results.reduce((acc, current ) => acc + current, 0);
    return sum
  }

  // todo
}

2

Answers


  1. You’re looking for Promise.race().

    function getPromise(): Promise<number> {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve(Math.round(Math.random() * 10))
            }, Math.random() * 100);
        })
    }
    
    const TIMEOUT_MS = 50;
    
    async function foo(): Promise<number> {
        // Create a timeout promise that rejects after TIMEOUT_MS
        const timeoutPromise = new Promise<never>((_, reject) => {
            setTimeout(() => reject('timeout'), TIMEOUT_MS);
        });
    
        // Start all three promises immediately
        const promise1 = getPromise();
        const promise2 = getPromise();
        const promise3 = getPromise();
    
        try {
            // Wait for all promises or timeout, whichever comes first
            await Promise.race([
                Promise.all([promise1, promise2, promise3]),
                timeoutPromise
            ]);
    
            // If we get here, all promises completed before timeout
            const results = await Promise.all([promise1, promise2, promise3]);
            return results.reduce((acc, current) => acc + current, 0);
        } catch (error) {
            // Timeout occurred, only wait for the first two promises
            const results = await Promise.all([promise1, promise2]);
            return results.reduce((acc, current) => acc + current, 0);
        }
    }
    

    Your approach of checking the timestamps could introduce a small delay because of how the event loop runs (the system is busy, the promise starts before timeBefore is recorded, …). They’re disconnected from the actual promise invocation.

    Whereas the approach above is directly linked to the Promise itself, not when the main thread resumes execution.

    Login or Signup to reply.
  2. If with the "the sum of the first 2" you mean the values of the first two promises that resolve, then you can do the following:

    Create a fourth promise that expires after 50ms and resolves to the value 0, and then wait for three of those four promises to resolve: sum up the values they resolve to for getting the final result. If the 50ms timeout is one of those first three to resolve, then you’ll have effectively summed up two of the values of the three given promises (plus 0). If the 50ms-promise is not one of those first three to resolve, then you have all three values of the three given promises. In both cases you get what is asked for:

    const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
    
    const getPromise = () => delay(Math.random() * 100).then(() => Math.round(Math.random() * 10));
    
    const TIMEOUT_MS = 50;
    
    async function foo() {
        const timeout = delay(TIMEOUT_MS).then(() => 0);
        // Create the three promises, and add the timeout promise to the set.
        // Map each of them to a new promise that also has the index of the promise in its fulfillment value
        const promises = [getPromise(), getPromise(), getPromise(), timeout].map((p, i) => Promise.all([p, i]));
        // Just for debugging: output when a promise resolves:
        for (const p of promises) p.then(console.log, console.error);
        
        let sum = 0;
        // Wait for the first three promises to resolve
        for (let j = 0; j < 3; j++) {
            const [value, i] = await Promise.race(promises.filter(Boolean));
            promises[i] = null; // Exclude this promise from the next race
            sum += value;
        }
        return sum;
    }
    
    foo().then(sum => console.log("Sum:", sum));

    Note that the above snippet also outputs for each promise — as soon as it gets fulfilled — an array with two elements:

    1. The value that the promise resolved with
    2. The index of the promise (0, 1, 2 or 3). Index 3 refers to the promise that takes care of the 50ms timeout.

    This allows to verify that the sum is output after three out of four promises have resolved.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search