Understanding promise.race() usage

后端 未结 6 1406
隐瞒了意图╮
隐瞒了意图╮ 2020-12-09 17:14

As far as I know, there are two options about promise:

  • promise.all()

  • promise.race()

Ok, I know what promise.all()<

相关标签:
6条回答
  • 2020-12-09 17:54

    It's a piece to build a timeout system, where:

    1. the request/computation may be canceled by another channel
    2. it will still be used later, but we need an interaction now.

    For an example of the second, one might show a spinner "instantly" while still defaulting to show real content if it comes in fast enough. Try running the below a few times - note at least some console message comes "instantly". This might normally be attached to perform operations on a UI.

    The key to note is - the result of Promise.race is much less important than the side effects (though, this then is a code smell).

    // 300 ms _feels_ "instant", and flickers are bad
    
    function getUserInfo(user) {
      return new Promise((resolve, reject) => {
        // had it at 1500 to be more true-to-life, but 900 is better for testing
        setTimeout(() => resolve("user data!"), Math.floor(900*Math.random()));
      });
    }
    
    function showUserInfo(user) {
      return getUserInfo().then(info => {
        console.log("user info:", info);
        return true;
      });
    }
    
    function showSpinner() {
      console.log("please wait...")
    }
    
    function timeout(delay, result) {
      return new Promise(resolve => {
        setTimeout(() => resolve(result), delay);
      });
    }
    Promise.race([showUserInfo(), timeout(300)]).then(displayed => {
      if (!displayed) showSpinner();
    });

    Inspiration credit to a comment by captainkovalsky.

    An example of the first:

    function timeout(delay) {
      let cancel;
      const wait = new Promise(resolve => {
        const timer = setTimeout(() => resolve(false), delay);
        cancel = () => {
          clearTimeout(timer);
          resolve(true);
        };
      });
      wait.cancel = cancel;
      return wait;
    }
    
    
    function doWork() {
      const workFactor = Math.floor(600*Math.random());
      const work = timeout(workFactor);
      
      const result = work.then(canceled => {
        if (canceled)
          console.log('Work canceled');
        else
          console.log('Work done in', workFactor, 'ms');
        return !canceled;
      });
      result.cancel = work.cancel;
      return result;
    }
    
    function attemptWork() {
      const work = doWork();
      return Promise.race([work, timeout(300)])
        .then(done => {
          if (!done)
            work.cancel();
          return (done ? 'Work complete!' : 'I gave up');
      });
    }
    
    attemptWork().then(console.log);

    You can see from this one that the timeout's console.log is never executed when the timeout hits first. It should fail/succeed about half/half, for testing convenience.

    0 讨论(0)
  • 2020-12-09 17:59

    I've used it for request batching. We had to batch tens of thousands of records into batches for a long running execution. We could do it in parallel, but didn't want the number of pending requests to get out of hand.

    Race lets us keep a fixed number of parallel promises running and add one to replace whenever one completes

    const _ = require('lodash')
    
    async function batchRequests(options) {
        let query = { offset: 0, limit: options.limit };
    
        do {
            batch = await model.findAll(query);
            query.offset += options.limit;
    
            if (batch.length) {
                const promise = doLongRequestForBatch(batch).then(() => {
                    // Once complete, pop this promise from our array
                    // so that we know we can add another batch in its place
                    _.remove(promises, p => p === promise);
                });
                promises.push(promise);
    
                // Once we hit our concurrency limit, wait for at least one promise to
                // resolve before continuing to batch off requests
                if (promises.length >= options.concurrentBatches) {
                    await Promise.race(promises);
                }
            }
        } while (batch.length);
    
        // Wait for remaining batches to finish
        return Promise.all(promises);
    }
    
    batchRequests({ limit: 100, concurrentBatches: 5 });
    
    0 讨论(0)
  • 2020-12-09 18:05

    Here's an easy example to understand the use of promise.race():

    Imagine you need to fetch some data from a server and if the data takes too long to load (say 15 seconds) you want to show an error.

    You would call promise.race() with two promises, the first being your ajax request and the second being a simple setTimeout(() => resolve("ERROR"), 15000)

    0 讨论(0)
  • 2020-12-09 18:06

    As you see, the race() will return the promise instance which is firstly resolved or rejected:

    var p1 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 500, 'one'); 
    });
    var p2 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 100, 'two'); 
    });
    
    Promise.race([p1, p2]).then(function(value) {
      console.log(value); // "two"
      // Both resolve, but p2 is faster
    });

    For a scenes to be used, maybe you want to limit the cost time of a request :

    var p = Promise.race([
        fetch('/resource-that-may-take-a-while'),
        new Promise(function (resolve, reject) {
             setTimeout(() => reject(new Error('request timeout')), 5000)
        })
    ])
    p.then(response => console.log(response))
    p.catch(error => console.log(error))
    

    With the race() you just need to get the returned promise, you needn't care about which one of the promises in the race([]) firstly returned,

    However, without the race, just like your example, you need to care about which one will firstly returned, and called the callback in the both success callback.

    0 讨论(0)
  • 2020-12-09 18:07

    Summary:

    Promise.race is a JS built in function that accepts an iterable of Promises (e.g. Array) as an argument. This function then asynchronously returns a Promise as soon as one in of the Promises passed in the iterable is either resolved or rejected.

    Example 1:

    var promise1 = new Promise((resolve, reject) => {
        setTimeout(() => resolve('Promise-one'), 500);
    });
    
    var promise2 = new Promise((resolve, reject) => {
        setTimeout(() => resolve('Promise-two'), 100);
    });
    
    Promise.race([promise1, promise2]).then((value) => {
      console.log(value);
      // Both resolve, but promise2 is faster than promise 1
    });

    In this example first an array of Promises is passed in Promise.race. Both of the promises resolve but promise1 resolves faster. Therefore the promise is resolved with the value of promise1, which is the string 'Promise-one'.

    Example 2:

    const promise1 = new Promise((resolve, reject) => {
        setTimeout(() => resolve('succes'), 2000);
    });
    
    const promise2 = new Promise((resolve, reject) => {
        setTimeout(() => reject('err'), 1000);
    });
    
    Promise.race([promise1, promise2])
      .then((value) => {
      console.log(value);
    }).catch((value) => {
      console.log('error: ' + value);
    });

    In this second example the second promise rejects faster than the first promise can resolve. Therefore Promise.race will return a rejected promise with the value of 'err' which was the value that Promise2 rejected with.

    The key point to understand is that Promice.race takes an iterable of Promises and returns a Promise based on the first resolved or rejected promise in that iterable (with the corresponding resolve() or reject() values).

    0 讨论(0)
  • 2020-12-09 18:19

    Let's take an sample workaround of Promise.race like below.

    const race = (promises) => {
        return new Promise((resolve, reject) => {
            return promises.forEach(f => f.then(resolve).catch(reject));
        })
    };
    

    You can see race function executes all promises, but whomever finishes first will resolve/reject with wrapper Promise.

    0 讨论(0)
提交回复
热议问题