The goal of this article is to use an easy-to-understand, easy-to-remember approach to understanding promise-related application technology points for use in resumes and interviews. This article is more practical than other big guy’s articles, and will try to improve the code specification and quality as much as possible (personal level is limited to give an optimal solution). As the saying goes, bite off more than you can chew. If you want to know more about the implementation methods and details, you can read more excellent articles.

Timeout control

background

  1. As we all know, it is not possible to set a timeout for a FETCH request, so we need to simulate one ourselves.
  2. The turntable problem, a draw turntable animation effect is 5 seconds, but generally the back end request the turntable result takes less than a second, so the request result must wait at least 5 seconds to show to the user.

Problem analysis

First, timeout control is simple, similar to the idea of promise.race (), or you can use this function directly.

And then, if you want to do the turntable problem well, you have to consider two scenarios.

  1. The turntable animation has not been completed, and the request result has been obtained. At this time, the result will be shown to the user after the animation is completed.

  2. The turntable animation is complete, the request result is not available, and you need to wait for the result to return (you can set the request timeout).

So, the turntable problem is better solved with promise.all ().

Actual version source code

The code is divided into multiple versions, from top to bottom, the memory difficulty increases but the interview result is better, please choose according to your needs.

Time out control based on promise.race ().

/** * Timeout control version 1 */

/** * helper function that encapsulates a promise *@param {number} Delay Delay time *@returns {Promise<any>}* /
function sleep(delay) {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > reject(new Error("timeout")), delay);
  });
}

/** * Wrap the original promise into a promise * with a timeout control@param {()=>Promise<any>} RequestFn Specifies the request function@param {number} Timeout Maximum timeout period *@returns {Promise<any>}* /
function timeoutPromise(requestFn, timeout) {
  return Promise.race([requestFn(), sleep(timeout)]);
}

// ---------- Here is the test case ------------

// Simulate an asynchronous request function
function createRequest(delay) {
  return () = >
    new Promise((resolve) = > {
      setTimeout(() = > {
        resolve("done");
      }, delay);
    });
}

// Examples of timeouts
timeoutPromise(createRequest(2000), 1000).catch((error) = >
  console.error(error)
);
// Examples of no timeouts
timeoutPromise(createRequest(2000), 3000).then((res) = > console.log(res));

Copy the code

2. Kill promise.race().

/** * Timeout control version 2 */

/** * helper function that encapsulates a promise *@param {number} Delay Delay time *@returns {Promise<any>}* /
function sleep(delay) {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > reject(new Error("timeout")), delay);
  });
}

/** * Wrap the original promise into a promise * with a timeout control@param {()=>Promise<any>} RequestFn Specifies the request function@param {number} Timeout Maximum timeout period *@returns {Promise<any>}* /
function timeoutPromise(requestFn, timeout) {
  const promises = [requestFn(), sleep(timeout)];
  return new Promise((resolve, reject) = > {
    for (const p of promises) {
      p.then((res) = > resolve(res)).catch((error) = >reject(error)); }}); }// ---------- Here is the test case ------------

// Simulate an asynchronous request function
function createRequest(delay) {
  return () = >
    new Promise((resolve) = > {
      setTimeout(() = > {
        resolve("done");
      }, delay);
    });
}

// Examples of timeouts
timeoutPromise(createRequest(2000), 1000).catch((error) = >
  console.error(error)
);
// Examples of no timeouts
timeoutPromise(createRequest(2000), 3000).then((res) = > console.log(res));

Copy the code

All () : Promise () : Promise () : Promise () : Promise () : Promise ();

/** * Turntable issues do not consider timeouts */

/** * helper function that encapsulates a promise *@param {number} Delay Delay time *@returns {Promise<any>}* /
function sleep(delay) {
  return new Promise((resolve) = > {
    setTimeout(() = > resolve(delay), delay);
  });
}

/** * Package the original promise as a turntable@param {()=>Promise<any>} RequestFn Specifies the request function@param {number} AnimationDuration Duration of an animation *@returns {Promise<any>}* /
function turntablePromise(requestFn, animationDuration) {
  return Promise.all([requestFn(), sleep(animationDuration)]);
}

// ---------- Here is the test case ------------

// Simulate an asynchronous request function
function createRequest(delay) {
  return () = >
    new Promise((resolve) = > {
      setTimeout(() = > {
        resolve("done");
      }, delay);
    });
}

// Request is faster than rotary animation
turntablePromise(createRequest(2000), 5000).then((res) = > console.log(res));

// Request is slower than rotary animation
turntablePromise(createRequest(2000), 1000).then((res) = > console.error(res));

Copy the code

Four: based on promise.all () the turntable problem (considering the request timeout), nothing more than a knife knife is not bright.

/** * Turntable issues to consider timeout */

/** * Wrap the original promise into a promise * with a timeout control@param {Promise<any>} Request Your request *@param {number} Timeout Maximum timeout period *@returns {Promise<any>}* /
function timeoutPromise(request, timeout) {
  function sleep(delay) {
    return new Promise((resolve, reject) = > {
      setTimeout(() = > reject(new Error("timeout")), delay);
    });
  }
  const promises = [request, sleep(timeout)];
  return new Promise((resolve, reject) = > {
    for (const p of promises) {
      p.then((res) = > resolve(res)).catch((error) = >reject(error)); }}); }/** * Package the original promise as a turntable@param {()=>Promise<any>} RequestFn Specifies the request function@param {number} Timeout Indicates the timeout period *@param {number} AnimationDuration Duration of an animation *@returns {Promise<any>}* /
function turntablePromise(requestFn, timeout, animationDuration) {
  function sleep(delay) {
    return new Promise((resolve) = > {
      setTimeout(() = > resolve(delay), delay);
    });
  }
  return Promise.all([timeoutPromise(requestFn(), timeout), sleep(animationDuration)]);
}

// ---------- Here is the test case ------------

// Simulate an asynchronous request function
function createRequest(delay) {
  return () = >
    new Promise((resolve) = > {
      setTimeout(() = > {
        resolve("done");
      }, delay);
    });
}

// The request was slower than the turntable animation and timed out
turntablePromise(createRequest(2000), 1500.1000).catch((error) = >
  console.error(error)
);

Copy the code

Five: kill promise.all (), this version of the code does not add any core things, just write the API, so I leave it to you to test.

Cancel duplicate request

background

When users frequently click a search Button, a large number of search requests will be issued in a short time, which will cause some pressure on the server. At the same time, the rendered data will not be as expected due to the different order of requests and responses. Here, we can use stabilization to reduce server stress, but it does not solve the latter problem very well.

Problem analysis

The essence of the problem is that requests of the same kind are made in order (in terms of the order of button clicks), but the response order is unpredictable, and we usually only want to render the data in response to the last request, while discarding the rest of the data. Therefore, we need to discard (or not process) the response data for requests other than the last request.

Actual version source code

In fact, Axios has a very good practice, you can use the article to eat. The implementation of canceling promise here uses the techniques of the previous chapter, and in Axios, because all asynchrons are issued by XHR, the implementation of Axios also uses xhr.abort() to cancel a request.

/** * Cancel request */

function CancelablePromise() {
  this.pendingPromise = null;
}

// Wrap a request and cancel duplicate requests
CancelablePromise.prototype.request = function (requestFn) {
  if (this.pendingPromise) {
    this.cancel("Cancel duplicate requests");
  }
  const _promise = new Promise((resolve, reject) = > (this.reject = reject));
  this.pendingPromise = Promise.race([requestFn(), _promise]);
  return this.pendingPromise;
};

Cancel the current request
CancelablePromise.prototype.cancel = function (reason) {
  this.reject(new Error(reason));
  this.pendingPromise = null;
};

// ---------- Here is the test case ------------

// Simulate an asynchronous request function
function createRequest(delay) {
  return () = >
    new Promise((resolve) = > {
      setTimeout(() = > {
        resolve("done");
      }, delay);
    });
}


const cancelPromise = new CancelablePromise();

// The first four requests will be automatically cancelled
for (let i = 0; i < 5; i++) {
  cancelPromise
    .request(createRequest(1000))
    .then((res) = > console.log(res)) // The last one done
    .catch((err) = > console.error(err)); // The first four errors: cancel duplicate requests
}

// Set a timer for 3s to allow all previous requests to be processed before continuing the test
setTimeout(() = > {
  // Cancel the last request manually
  cancelPromise
    .request(createRequest(1000))
    .then((res) = > console.log(res))
    .catch((err) = > console.error(err)); // Error: Manually cancel
  cancelPromise.cancel("Manually cancel");
}, 3000);

// Set a timer for 4s to allow all previous requests to be processed before continuing the test
setTimeout(() = > {
  cancelPromise
    .request(createRequest(1000))
    .then((res) = > console.log(res)) // done
    .catch((err) = > console.error(err));
}, 4000);

Copy the code

Limit the number of concurrent requests

background

In general, we don’t try to control the concurrency of requests. This is only used in some scenarios, such as collecting user operations in batches (each operation is a request) and sending them once the user operations are complete. In addition, in order to reduce the strain on the server, we also limit the number of concurrent requests.

Problem analysis

Promise.allSettled might seem well placed to handle such a situation, but on second thought, promise. allSettled has far too much granularity to handle. First, it has to wait for all promises to be resolved or rejected. Second, it has to batch requests if there are concurrency limits, which makes it less efficient. The short board effect is obvious.

Actual version source code

** * Limit the number of concurrent requests */

/** * Limit the number of concurrent requests *@param {()=>Promise<any> []} RequestFns Array of concurrent request functions *@param {numer} Limit Specifies the maximum number of concurrent requests */
function concurrentRequest(requestFns, limit) {
  // Recursive function
  function recursion(requestFn) {
    requestFn().finally(() = > {
      if (_requestFns.length > 0) { recursion(_requestFns.pop()); }}); }const _requestFns = [...requestFns];
  // Limit the maximum concurrency
  for (let i = 0; i < limit && _requestFns.length > 0; i++) { recursion(_requestFns.pop()); }}// ---------- Here is the test case ------------

// Simulate an asynchronous request function
function createRequest(delay) {
  return () = >
    new Promise((resolve) = > {
      setTimeout(() = > {
        resolve("done");
      }, delay);
    }).then((res) = > {
      console.log(res);
    });
}

const requestFns = [];
for (let i = 0; i < 10; i++) {
  requestFns.push(createRequest(1000));
}

concurrentRequest(requestFns, 3);

Copy the code

Managing global loading mode

background

When a page or component is involved with multiple requests, it is possible to manage multiple loading states. In some scenarios, we only want to use one loading state to manage all asynchronous requests. If any of the pending requests exist, the global loading component is displayed. If all the requests are fulfilled or rejected, the global loading component is hidden.

Problem analysis

The key to this problem is that we need to manage all pending state requests and update loading state timely.

Actual version source code

/** * Manage global loading mode */

function PromiseManager() {
  this.pendingPromise = new Set(a);this.loading = false;
}

// Generate an identity flag for each promise in the pending state
PromiseManager.prototype.generateKey = function () {
  return `The ${new Date().getTime()}-The ${parseInt(Math.random() * 1000)}`;
};

PromiseManager.prototype.push = function (. requestFns) {
  for (const requestFn of requestFns) {
    const key = this.generateKey();
    this.pendingPromise.add(key);
    requestFn().finally(() = > {
      this.pendingPromise.delete(key);
      this.loading = this.pendingPromise.size ! = =0; }); }};// ---------- Here is the test case ------------

// Simulate an asynchronous request function
function createRequest(delay) {
  return () = >
    new Promise((resolve) = > {
      setTimeout(() = > {
        resolve("done");
      }, delay);
    }).then((res) = > console.log(res));
}

const manager = new PromiseManager();

// Add multiple requests
manager.push(createRequest(1000), createRequest(2000), createRequest(3000));
manager.push(createRequest(1500));

// Poll the loading mode every second until Loading is false
const id = setInterval(() = > {
  console.log(manager.loading);
  if(! manager.loading)clearInterval(id);
}, 1000);

// Add multiple requests
manager.push(createRequest(2500));
Copy the code

snacks

Three ways to implement serialization

A common scenario with serialization is that there are dependencies or timing relationships between requests, such as traffic lights.

/** * serialization of three implementations **/

// Method 1, recursive method
function runPromiseInSeq1(requestFns) {
  function recursion(requestFns) {
    if (requestFns.length === 0) return;
    requestFns
      .shift()()
      .finally(() = > recursion(requestFns));
  }
  const _requestFns = [...requestFns];
  recursion(_requestFns);
}
// Method 2: iterative method
async function runPromiseInSeq2(requestFns) {
  for (const requestFn of requestFns) {
    awaitrequestFn(); }}// Method 3: reduce
function runPromiseInSeq3(requestFns) {
  requestFns.reduce((pre, cur) = > pre.finally(() = > cur()), Promise.resolve());
}

// Simulate an asynchronous request function
function createRequest(delay) {
  return () = >
    new Promise((resolve) = > {
      setTimeout(() = > {
        resolve(delay);
      }, delay);
    }).then((res) = > {
      console.log(res);
    });
}
// Execute from left to right
const requestFns = [
  createRequest(3000),
  createRequest(2000),
  createRequest(1000)];// Serial calls
runPromiseInSeq1(requestFns);
// runPromiseInSeq2(requestFns);
// runPromiseInSeq3(requestFns);

Copy the code

Line 20 minimum asynchronous chained call

This emulates an asynchronous chained call to a Promise. See article for the source.

function Promise(fn) {
  this.cbs = [];

  const resolve = (value) = > {
    setTimeout(() = > {
      this.data = value;
      this.cbs.forEach((cb) = > cb(value));
    });
  }

  fn(resolve);
}

Promise.prototype.then = function (onResolved) {
  return new Promise((resolve) = > {
    this.cbs.push(() = > {
      const res = onResolved(this.data);
      if (res instanceof Promise) {
        res.then(resolve);
      } else{ resolve(res); }}); }); };Copy the code