Topic describes

Implement a wrapper ajaxer, functions are

  1. Limit the number of Simultaneous Ajax requests to m
  2. The timeout limit
  3. Retry n times

The solution

The previous solution, which I posted on my blog, was to implement queued, cancelable promises to build on, but the ajaxer part of the code was too coupled to leave it there.

Recently, I saw how to implement the Promise limit: the simple implementation of promise.map. I felt that the code I wrote was too long, like an old woman’s foot-binding. It was very simple to write a loose code that could realize the function, but it was very difficult to write a simple code that was easy to reuse and easy to expand. So I rewrote it. My ideas and code are just an attempt to answer the interview questions. They are not perfect solutions, but I hope they can give you some thoughts and give you a thumbs up if you find them useful.

Implementation approach

  • Removing the queue class is not necessary, it can be easily implemented with arrays, but this can cause performance problems because arrays grow indefinitely, and when they grow to a certain level, performance is not good.
  • The most important implementation is to set a Promise in the outer layer (temporarily called the master Promise). The external asynchronous processing is also based on the master Promise, adding the resolve and reject functions of the master Promise into the queue. But the real requests are made in internally nested promises (tentatively called child promises), which change the state of the master Promise based on its own state.
  • The implementation of limiting the flow can suspend the main Promise and not make the actual request. Wait until one Promise completes, then check the queue and execute the sub-promises.
  • The implementation of the timeout, like before, puts a timer in the child Promise, then the rejected
  • Retry implementation, maintain the child Promise, the child Promise state changed to rejected, check whether there is a chance to retry, then create a child Promise to the main Promise, join the request queue. Here is the implementation of a little not concise, has been passing parameters, to be improved.
  • There is a slight improvement, however, in that the timeout and retry functions are written as configurable, that is, when the request needs to be retried, it is passed in again. If you want to provide other functions in the future, you can add functions to improve the scalability.

Take a look at the usage:

  const mapUrl = function(){
    const a = new Axios(3);
    for (let url of arguments) {
        a.post(url,Axios.timeout(300),Axios.retry(3)).then(value= >{
            console.log(value);
        }).catch((e) = >{
            console.log(url+e.message);
        });
    }
}

mapUrl("baidu1.com"."baidu2.com"."baidu3.com"."baidu4.com"."baidu5.com"."baidu6.com");
Copy the code

The implementation code

There is less code in this edition

class Axios {
    constructor (n) {
      this.limit = n
      this.count = 0
      this.queue = []
    }
  
    enqueue (fn,promise=null,resolve=null,reject=null) {
      if(promise){
        this.queue.push({fn, resolve, reject});
        this.queue.push(promise);
        return promise;
      }
      / / the main Promise
      let p = new Promise((resolve, reject) = > {
        this.queue.push({fn, resolve, reject })
      })
      this.queue.push(p);
      return p;
    }
  
    dequeue () {
    // Wait until the Promise counter is less than the threshold, then queue out
      if (this.count < this.limit && this.queue.length) {
        const { fn, resolve, reject} = this.queue.shift();
        const p = this.queue.shift();
        this.run(p, fn,resolve,reject); }}// async/await simplifies error handling
    async run (p, fn,resolve,reject) {
      try{
          this.count++
          const value = await fn(p,resolve,reject);
          this.count--;
          this.dequeue();
          // Release the master Promise and change the state to Resolved
          resolve(value);
      }catch(e){
          this.count--;
          this.dequeue();
      }
    }
  
    build (fn,promise) {
        let p = this.enqueue(fn,... promise);this.dequeue();
        return p;
    }
  
    post(url){
      let fns = [],len = arguments.length- 1,parentPromise=[],hasRetry=false;
      if(arguments.length>1){
          url = arguments[0];
          if(len>2&&arguments[len2 -] instanceof Promise){
            parentPromise = [].slice.call(arguments,len2 -);
            fns = [].slice.call(arguments.0,len2 -);
          }else{
            fns = [].slice.call(arguments.0); }}// Sub-promise, the place where the real request is made
      let request = (. parentP) = >{
              let res,rej,promise;
              // Simulate POST request, local test is convenient
              promise = new Promise((resolve, reject) = > {
                  res = resolve;
                  rej = reject;
                  setTimeout((a)= >{
                      resolve(url+"Executed successfully");
                  }, Math.random()*500);
                  setTimeout((a)= >{
                    reject(url+"Execution failed");
                }, Math.random()*400);
              })// End of simulation
              // Add handlers to it (error retries, etc.)
              for (const fn of fns) {
                  if(typeof(fn) ===  "function"){
                    fn.bind(this)(promise,res,rej,fns,parentP);
                    if(fn.name === Axios.retry.name){
                        hasRetry = true; }}}// The retry function is added by default, and state changes rejected are handled only in this function
              if(! hasRetry){ Axios.retry(0).bind(this)(promise,res,rej,fns,parentP);
              }
              return promise;
          };
      return this.build(request,parentPromise);
    }
  
  
    // Error retry
    static retry(times){
      return function retry(promise,resolve,reject,args,parentP){
          promise.catch((v) = >{
             if(this instanceof Axios&&times>0) {for (let a of args) {
                  if(typeof(a) ===  "function"&& a.name === Axios.retry.name){ times--; a = Axios.retry(times); }}// If it fails, add it to the request queue
              console.log(args[0] +"Try again");
              this.post(... args,... parentP); }else{
                 // Release the host Promise and change the state to Rejected
                 parentP[parentP.length- 1] (new Error("Error retry failed",v)); }}); }}/ / the timeout limits
    static timeout(time){
      return function timeout(promise,resolve,reject,args){
          let t = setTimeout((a)= >{
              console.log(args[0] +"Timeout");
              reject(args[0] +"Timeout");
          },time);
          promise.then((a)= >{
              clearTimeout(t);
              t = null;
          }).catch((a)= >{
              clearTimeout(t);
              t = null; }); }}}Copy the code

The result is as follows:

Author dish, if have wrong, please point out quickly, many forgive!