preface

The Promise Race method is one of the easier methods to use when using promises. According to MDN’s definition of Promise Race,

The Promise.race(iterable) method returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects, with the value or reason from that promise.

In its literal sense, the promise.race method returns a Promise that has been resolved. The resolved value is the fastest resolved Promise value or the rejected value.

In other words, give promise.race an array of promises as input, with the fastest resolved value returned as the Promise’s resolve or Rejected value.

Examples of code posted in MDN are as follows:

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, 'one');
});

var promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(function(value) {
  console.log(value);
  // Both resolve, but promise2 is faster
});
// expected output: "two"
Copy the code

Easy to misunderstand

In the code above, there is a comment, “Both resolve, but promise2 is faster”, so the desired result is “two”. This creates the illusion that any promise that is fast must return its resolve value. There are a couple of prerequisites.

  1. Promise.race must be invoked after defined promises whenever possible.
  2. In some cases, promisE2 doesn’t necessarily return its value even if it’s faster.

Here are two more examples of promising. Race errors.

  • Promise. Race must be defined as far as possible in the PromisetheCall later.

We changed the MDN code slightly so that promise.race would not be executed immediately, but the next execution cycle.

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, 'one');
});

var promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, 'two'); }); // In this case, I used a timer callback promise.race method. // This timer is exactly the maximum amount of time that two promises will wait for, i.e. 500ms. // Console. log(value) is associated only with the first promise, even if promise2 is faster than promise1, the result is still "one".setTimeout(()=> {
  Promise.race([promise1, promise2]).then(function(value) { console.log(value); }); }, 500).Copy the code
  • In some cases, promisE2 doesn’t necessarily return its value even if it’s faster.

Let’s make some adjustments to the MDN code as follows:

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 1, 'one');
});

var promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 0, 'two');
});



Promise.race([promise1, promise2]).then(function(value) {
  console.log(value);
  // Both resolve, but promise2 is faster
});
Copy the code

The code above is more extreme, but it does reflect a few things. Promise2 is still faster, but returns “one”. If I set promise1 to be greater than or equal to 2, I will return “two”. Hope to know the god of supplementary explanation. I will continue to study the operation mechanism of setTimeout in the future.

The original sin

Why does this result in error? We can start by looking at the Promise. Race implementation source code.

Cdn.jsdelivr.net/npm/es6-pro…

function race(entries) {
  /*jshint validthis:true*/ var Constructor = this; // This is the Promise constructor function that calls race.if(! isArray(entries)) {return new Constructor(function (_, reject) {
      return reject(new TypeError('You must pass an array to race.'));
    });
  } else {
    return new Constructor(function (resolve, reject) {
      var length = entries.length;
      for(var i = 0; i < length; i++) { Constructor.resolve(entries[i]).then(resolve, reject); }}); }}Copy the code

So race works by iterating [promise1, promise2…] “, and resolve each promise in order. Note that a race is not strictly a fair race, in which there is always a false start. In this case, promise1 executes its executor first and then is traversed by promise.race when race is invoked. Therefore, the promise defined first and the promise placed in front of the array always have the first chance to be resolved.

So, here, if we don’t take into account the nature of race’s sequential traversal of promise instances, it’s possible to get unexpected results, as in the counterexamples I’ve listed above.

In the first column, promise1 will theoretically resolve after 500 milliseconds, and promisE2 will theoretically resolve after 100 milliseconds. I set a 500 millisecond timer for promise. race. The 500 millisecond timer gives promise1 enough time to resolve, so even if promise2 resolves faster, the result is still the same as the result of promise1.

In the second example, when we call promise.race, we know from the race source code that Race will pass the first completed Promise to resolve by passing resolve to then. Then is an asynchronous method, meaning that after calling THEN, resolve or reject is executed in the next cycle, which gives a chance for some promises to end in the short term. Thus, if the setTimeout in a Promise is short enough, then on the first call to THEN, the preceding Promise will resolve first, even if the Promise behind the array has a shorter setTimeout. Resolve is the first resolved value.

conclusion

To avoid unintended consequences when using promises, we need to try to invoke promise.race as soon as we define the promises. This tip does not guarantee that promise.race will fairly return the fastest resolve value, such as:

let promises = [];

for (let i = 30000; i > -1; i-- ) {
  promises.push(new Promise(resolve => {    
    setTimeout(resolve, i, i);
  }))
}

Promise.race(promises).then(function(value) {
  console.log(value);
  // Both resolve, but promise2 is faster
});
Copy the code

Although promise.race is invoked immediately after all promises are defined, due to the large number of promises, the value of resolve becomes dependent on traversal order and execution speed above a certain threshold.

In short, promise. race iterates sequentially, and then puts callbacks into the event queue, so that the callbacks are called sequentially, and if the callbacks of the THEN methods in the event queue have not yet finished executing, Promise. Race returns the fastest resolve value, but if some asynchronous operation that executes quickly returns a result before all the THEN callbacks have been iterated, it can result in asynchronous returns that are fast, But because of the slow asynchronous operation in the event queue, you get an unexpected return result.