Interviewer: please write a delay function with cancellation function. What is the principle of axios cancellation function

Posted by GremlinP1R on Wed, 22 Dec 2021 15:49:16 +0100

Hello, I'm Ruokawa . Recently organized Source code co reading activity Every week, we learn about 200 lines of source code and make common progress. At the same time, it is highly recommended to subscribe to what I wrote Learning source code overall architecture series Contains more than 20 source articles.

Paper warehouse https://github.com/lxchuan12/delay-analysis.git , find a star^_^[1]

Source code co reading activity Once a week, it has reached phase 17. So we search for all kinds of source code worthy of our study and few lines of code. The delay master file has only more than 70 lines [2], which is very worth learning.

After reading this article, you will learn:

1. Learn how to achieve a more perfect delay function
2. Learn to use AbortController Implement cancellation function
3. Learn to interview frequently axios Cancel function implementation
4. wait

2. Environmental preparation

# It is recommended to clone my project to ensure synchronization with the article
git clone https://github.com/lxchuan12/delay-analysis.git
# npm i -g yarn
cd delay-analysis/delay && yarn i
# VSCode directly opens the current project
# code .
# All the examples I wrote are in the examples folder. You can start the service and view the debugging locally
# In the delay analysis directory
npx http-server examples
# Open http://localhost:8080

# Or clone the official project
git clone https://github.com/sindresorhus/delay.git
# npm i -g yarn
cd delay && yarn i
# VSCode directly opens the current project
# code .

3. delay

We implement a perfect delay function from scratch [3].

3.1 delay of the first edition

To complete such a delay function.

3.1. 1 use

(async() => {
    await delay1(1000);
    console.log('Output this sentence');
})();

3.1. 2 implementation

Using Promise and setTimeout, we can easily implement the following code.

const delay1 = (ms) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, ms);
    });
}

We need to deliver the results.

3.2 the second version passes the value parameter as the result

3.2. 1 use

(async() => {
    const result = await delay2(1000, { value: 'I'm ruokawa' });
    console.log('Output results', result);
})();

We can also easily implement the following code. Pass value and return it as a result.

3.2. 2 implementation

Therefore, we can easily implement the second version as follows.

const delay2 = (ms, { value } = {}) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(value);
        }, ms);
    });
}

In this way, Promise is always success. We also need to fail. At this time, we define a parameter willResolve.

3.3 the willResolve parameter in the third version determines success or failure.

3.3. 1 use

(async() => {
    try{
        const result = await delay3(1000, { value: 'I'm ruokawa', willResolve: false });
        console.log('Never output this sentence');
    }
    catch(err){
        console.log('Output results', err);
    }
})();

3.3. 2 implementation

Add a willResolve parameter to determine success or failure. So we have the following implementation.

const delay3 = (ms, {value, willResolve} = {}) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if(willResolve){
                resolve(value);
            }
            else{
                reject(value);
            }
        }, ms);
    });
}

3.4 the fourth edition randomly obtains results within a certain time range

The number of milliseconds of the delayer is write dead. We hope to obtain the results randomly in a certain time range.

3.4. 1 use

(async() => {
    try{
        const result = await delay4.reject(1000, { value: 'I'm ruokawa', willResolve: false });
        console.log('Never output this sentence');
    }
    catch(err){
        console.log('Output results', err);
    }

    const result2 = await delay4.range(10, 20000, { value: 'I'm ruokawa, range' });
    console.log('Output results', result2);
})();

3.4. 2 implementation

We encapsulate success delay and failure reject into a function, and random range into a function separately.

const randomInteger = (minimum, maximum) => Math.floor((Math.random() * (maximum - minimum + 1)) + minimum);

const createDelay = ({willResolve}) => (ms, {value} = {}) => {
    return new Promise((relove, reject) => {
        setTimeout(() => {
            if(willResolve){
                relove(value);
            }
            else{
                reject(value);
            }
        }, ms);
    });
}

const createWithTimers = () => {
    const delay = createDelay({willResolve: true});
    delay.reject = createDelay({willResolve: false});
    delay.range = (minimum, maximum, options) => delay(randomInteger(minimum, maximum), options);
    return delay;
}
const delay4 = createWithTimers();

So far, it is relatively perfect. But we may need to end early.

3.5 the fifth edition is cleared in advance

3.5. 1 use

(async () => {
    const delayedPromise = delay5(1000, {value: 'I'm ruokawa'});

    setTimeout(() => {
        delayedPromise.clear();
    }, 300);

    // 300 milliseconds later
    console.log(await delayedPromise);
    //=>'I'm ruokawa'
})();

3.5. 2 implementation

Declare the setting variable, encapsulate the setting function, and call delaypromise Clear the timer when clear. So we can get the following fifth version of the code.

const randomInteger = (minimum, maximum) => Math.floor((Math.random() * (maximum - minimum + 1)) + minimum);

const createDelay = ({willResolve}) => (ms, {value} = {}) => {
    let timeoutId;
    let settle;
    const delayPromise = new Promise((resolve, reject) => {
        settle = () => {
            if(willResolve){
                resolve(value);
            }
            else{
                reject(value);
            }
        }
        timeoutId = setTimeout(settle, ms);
    });

    delayPromise.clear = () => {
        clearTimeout(timeoutId);
  timeoutId = null;
  settle();
    };

    return delayPromise;
}

const createWithTimers = () => {
    const delay = createDelay({willResolve: true});
    delay.reject = createDelay({willResolve: false});
    delay.range = (minimum, maximum, options) => delay(randomInteger(minimum, maximum), options);
    return delay;
}
const delay5 = createWithTimers();

3.6 cancellation function of version 6

We can see from the data that AbortController can realize the cancellation function.

caniuse AbortController[4]

npm abort-controller[5]

mdn AbortController[6]

fetch-abort[7]

fetch#aborting-requests[8]

yet-another-abortcontroller-polyfill[9]

3.6. 1 use

(async () => {
    const abortController = new AbortController();

    setTimeout(() => {
        abortController.abort();
    }, 500);

    try {
        await delay6(1000, {signal: abortController.signal});
    } catch (error) {
        // 500 milliseconds later
        console.log(error.name)
        //=> 'AbortError'
    }
})();

3.6. 2 implementation

const randomInteger = (minimum, maximum) => Math.floor((Math.random() * (maximum - minimum + 1)) + minimum);

const createAbortError = () => {
 const error = new Error('Delay aborted');
 error.name = 'AbortError';
 return error;
};

const createDelay = ({willResolve}) => (ms, {value, signal} = {}) => {
    if (signal && signal.aborted) {
  return Promise.reject(createAbortError());
 }

    let timeoutId;
    let settle;
    let rejectFn;
    const signalListener = () => {
        clearTimeout(timeoutId);
        rejectFn(createAbortError());
    }
    const cleanup = () => {
  if (signal) {
   signal.removeEventListener('abort', signalListener);
  }
 };
    const delayPromise = new Promise((resolve, reject) => {
        settle = () => {
   cleanup();
   if (willResolve) {
    resolve(value);
   } else {
    reject(value);
   }
  };

        rejectFn = reject;
        timeoutId = setTimeout(settle, ms);
    });
    
    if (signal) {
  signal.addEventListener('abort', signalListener, {once: true});
 }

    delayPromise.clear = () => {
  clearTimeout(timeoutId);
  timeoutId = null;
  settle();
 };

    return delayPromise;
}

const createWithTimers = () => {
    const delay = createDelay({willResolve: true});
    delay.reject = createDelay({willResolve: false});
    delay.range = (minimum, maximum, options) => delay(randomInteger(minimum, maximum), options);
    return delay;
}
const delay6 = createWithTimers();

3.7 the seventh edition of user-defined clearTimeout and setTimeout functions

3.7. 1 use

const customDelay = delay7.createWithTimers({clearTimeout, setTimeout});

(async() => {
    const result = await customDelay(100, {value: 'I'm ruokawa'});

    // Executed after 100 milliseconds
    console.log(result);
    //=>'I'm ruokawa'
})();

3.7. 2 implementation

Pass the two parameters clearTimeout and setTimeout to replace the clearTimeout and setTimeout of the previous version. So there was the seventh edition. This is the final implementation of delay.

    const randomInteger = (minimum, maximum) => Math.floor((Math.random() * (maximum - minimum + 1)) + minimum);

const createAbortError = () => {
 const error = new Error('Delay aborted');
 error.name = 'AbortError';
 return error;
};

const createDelay = ({clearTimeout: defaultClear, setTimeout: set, willResolve}) => (ms, {value, signal} = {}) => {
    if (signal && signal.aborted) {
  return Promise.reject(createAbortError());
 }

    let timeoutId;
    let settle;
    let rejectFn;
    const clear = defaultClear || clearTimeout;

    const signalListener = () => {
        clear(timeoutId);
        rejectFn(createAbortError());
    }
    const cleanup = () => {
  if (signal) {
   signal.removeEventListener('abort', signalListener);
  }
 };
    const delayPromise = new Promise((resolve, reject) => {
        settle = () => {
   cleanup();
   if (willResolve) {
    resolve(value);
   } else {
    reject(value);
   }
  };

        rejectFn = reject;
        timeoutId = (set || setTimeout)(settle, ms);
    });
    
    if (signal) {
  signal.addEventListener('abort', signalListener, {once: true});
 }

    delayPromise.clear = () => {
  clear(timeoutId);
  timeoutId = null;
  settle();
 };

    return delayPromise;
}

const createWithTimers = clearAndSet => {
    const delay = createDelay({...clearAndSet, willResolve: true});
    delay.reject = createDelay({...clearAndSet, willResolve: false});
    delay.range = (minimum, maximum, options) => delay(randomInteger(minimum, maximum), options);
    return delay;
}
const delay7 = createWithTimers();
delay7.createWithTimers = createWithTimers;

4. axios cancellation request

axios cancels by passing the config configuration cancelToken. If it is judged that there is a cancelToken passed, an error is thrown in the dispatchRequest called in the promise chain, and the request in the adapter Abort() cancels the request, causes promise to go to rejected, and the user captures the cancellation information.

See more about my axios source code article cancellation module Learn the overall architecture of axios source code and cancel the module (click)

5. Summary

We implemented a perfect delay function with cancellation function from scratch. That is, the implementation of delay 70 multi line source code [11].

It supports random time end, early clearing, cancellation, custom clearTimeout, setTimeout and other functions.

mdn AbortController[12] is not used. Due to poor compatibility, the community also has a corresponding NPM abort controller [13] to implement polyfill.

yet-another-abortcontroller-polyfill[14]

It is recommended to clone the project startup service debugging example, which will be more impressive.

# It is recommended to clone my project to ensure synchronization with the article
git clone https://github.com/lxchuan12/delay-analysis.git
cd delay-analysis
# All the examples I wrote are in the examples folder. You can start the service and view the debugging locally
npx http-server examples
# Open http://localhost:8080

reference material

[1] Paper warehouse https://github.com/lxchuan12/delay-analysis.git , find a star ^ ^: https://github.com/lxchuan12/delay-analysis.git

[2] The delay master file has only more than 70 lines: https://github.com/sindresorhus/delay/blob/main/index.js