前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:请手写一个带取消功能的延迟函数,axios 取消功能的原理是什么

面试官:请手写一个带取消功能的延迟函数,axios 取消功能的原理是什么

作者头像
若川
发布2021-12-22 12:04:25
7160
发布2021-12-22 12:04:25
举报
文章被收录于专栏:若川视野若川视野

大家好,我是若川。最近组织了源码共读活动,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。

本文仓库 https://github.com/lxchuan12/delay-analysis.git,求个star^_^[1]

源码共读活动 每周一期,已进行到17期。于是搜寻各种值得我们学习,且代码行数不多的源码。delay 主文件仅70多行[2],非常值得我们学习。

阅读本文,你将学到:

代码语言:javascript
复制
1. 学会如何实现一个比较完善的 delay 函数
2. 学会使用 AbortController 实现取消功能
3. 学会面试常考 axios 取消功能实现
4. 等等

2. 环境准备

代码语言:javascript
复制
# 推荐克隆我的项目,保证与文章同步
git clone https://github.com/lxchuan12/delay-analysis.git
# npm i -g yarn
cd delay-analysis/delay && yarn i
# VSCode 直接打开当前项目
# code .
# 我写的例子都在 examples 这个文件夹中,可以启动服务本地查看调试
# 在 delay-analysis 目录下
npx http-server examples
# 打开 http://localhost:8080

# 或者克隆官方项目
git clone https://github.com/sindresorhus/delay.git
# npm i -g yarn
cd delay && yarn i
# VSCode 直接打开当前项目
# code .

3. delay

我们从零开始来实现一个比较完善的 delay 函数[3]

3.1 第一版 简版延迟

要完成这样一个延迟函数。

3.1.1 使用
代码语言:javascript
复制
(async() => {
    await delay1(1000);
    console.log('输出这句');
})();
3.1.2 实现

PromisesetTimeout 结合实现,我们都很容易实现以下代码。

代码语言:javascript
复制
const delay1 = (ms) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, ms);
    });
}

我们要传递结果。

3.2 第二版 传递 value 参数作为结果

3.2.1 使用
代码语言:javascript
复制
(async() => {
    const result = await delay2(1000, { value: '我是若川' });
    console.log('输出结果', result);
})();

我们也很容易实现如下代码。传递 value 最后作为结果返回。

3.2.2 实现

因此我们实现也很容易实现如下第二版。

代码语言:javascript
复制
const delay2 = (ms, { value } = {}) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(value);
        }, ms);
    });
}

这样写,Promise 永远是成功。我们也需要失败。这时我们定义个参数 willResolve 来定义。

3.3 第三版 willResolve 参数决定成功还是失败。

3.3.1 使用
代码语言:javascript
复制
(async() => {
    try{
        const result = await delay3(1000, { value: '我是若川', willResolve: false });
        console.log('永远不会输出这句');
    }
    catch(err){
        console.log('输出结果', err);
    }
})();
3.3.2 实现

加个 willResolve 参数决定成功还是失败。于是我们有了如下实现。

代码语言:javascript
复制
const delay3 = (ms, {value, willResolve} = {}) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if(willResolve){
                resolve(value);
            }
            else{
                reject(value);
            }
        }, ms);
    });
}

3.4 第四版 一定时间范围内随机获得结果

延时器的毫秒数是写死的。我们希望能够在一定时间范围内随机获取到结果。

3.4.1 使用
代码语言:javascript
复制
(async() => {
    try{
        const result = await delay4.reject(1000, { value: '我是若川', willResolve: false });
        console.log('永远不会输出这句');
    }
    catch(err){
        console.log('输出结果', err);
    }

    const result2 = await delay4.range(10, 20000, { value: '我是若川,range' });
    console.log('输出结果', result2);
})();
3.4.2 实现

我们把成功 delay 和失败 reject 封装成一个函数,随机 range 单独封装成一个函数。

代码语言:javascript
复制
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();

实现到这里,相对比较完善了。但我们可能有需要提前结束。

3.5 第五版 提前清除

3.5.1 使用
代码语言:javascript
复制
(async () => {
    const delayedPromise = delay5(1000, {value: '我是若川'});

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

    // 300 milliseconds later
    console.log(await delayedPromise);
    //=> '我是若川'
})();
3.5.2 实现

声明 settle变量,封装 settle 函数,在调用 delayPromise.clear 时清除定时器。于是我们可以得到如下第五版的代码。

代码语言:javascript
复制
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 第六版 取消功能

我们查阅资料可以知道有 AbortController 可以实现取消功能。

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 使用
代码语言:javascript
复制
(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 实现
代码语言:javascript
复制
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 第七版 自定义 clearTimeout 和 setTimeout 函数

3.7.1 使用
代码语言:javascript
复制
const customDelay = delay7.createWithTimers({clearTimeout, setTimeout});

(async() => {
    const result = await customDelay(100, {value: '我是若川'});

    // Executed after 100 milliseconds
    console.log(result);
    //=> '我是若川'
})();
3.7.2 实现

传递 clearTimeout, setTimeout 两个参数替代上一版本的clearTimeout,setTimeout。于是有了第七版。这也就是delay的最终实现。

代码语言:javascript
复制
    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 取消请求

axios取消原理是:通过传递 config 配置 cancelToken 的形式,来取消的。判断有传cancelToken,在 promise 链式调用的 dispatchRequest 抛出错误,在 adapterrequest.abort() 取消请求,使 promise 走向 rejected,被用户捕获取消信息。

更多查看我的 axios 源码文章取消模块 学习 axios 源码整体架构,取消模块(可点击)

5. 总结

我们从零开始实现了一个带取消功能比较完善的延迟函数。也就是 delay 70多行源码[11]的实现。

包含支持随机时间结束、提前清除、取消、自定义 clearTimeout、setTimeout等功能。

取消使用了 mdn AbortController[12] ,由于兼容性不太好,社区也有了相应的 npm abort-controller[13] 实现 polyfill

yet-another-abortcontroller-polyfill[14]

建议克隆项目启动服务调试例子,印象会更加深刻。

代码语言:javascript
复制
# 推荐克隆我的项目,保证与文章同步
git clone https://github.com/lxchuan12/delay-analysis.git
cd delay-analysis
# 我写的例子都在 examples 这个文件夹中,可以启动服务本地查看调试
npx http-server examples
# 打开 http://localhost:8080

参考资料

[1]本文仓库 https://github.com/lxchuan12/delay-analysis.git,求个star^_^: https://github.com/lxchuan12/delay-analysis.git

[2]delay 主文件仅70多行: https://github.com/sindresorhus/delay/blob/main/index.js

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-12-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 若川视野 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2. 环境准备
  • 3. delay
    • 3.1 第一版 简版延迟
      • 3.1.1 使用
      • 3.1.2 实现
    • 3.2 第二版 传递 value 参数作为结果
      • 3.2.1 使用
      • 3.2.2 实现
    • 3.3 第三版 willResolve 参数决定成功还是失败。
      • 3.3.1 使用
      • 3.3.2 实现
    • 3.4 第四版 一定时间范围内随机获得结果
      • 3.4.1 使用
      • 3.4.2 实现
    • 3.5 第五版 提前清除
      • 3.5.1 使用
      • 3.5.2 实现
    • 3.6 第六版 取消功能
      • 3.6.1 使用
      • 3.6.2 实现
    • 3.7 第七版 自定义 clearTimeout 和 setTimeout 函数
      • 3.7.1 使用
      • 3.7.2 实现
  • 4. axios 取消请求
  • 5. 总结
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档