模拟axios 写一个带取消功能的延迟函数

80 阅读3分钟

本文用到的技术点包含Promise、AbortController

整篇从易到难逐步增加功能,对技术一般的同学十分友好。

收获:

  • 提前执行、取消执行的思路。
  • 方法的提炼。

延迟输出

//  延迟输出 
const delay = (ms)=>{
    return new Promise((resolve, reject)=>{
        setTimeout(()=>{
            resolve()
        },ms)
    })
}


// 执行功能
(async function demo(){
    await delay(1000);
    console.log('延迟输出');
})()

增加value参数做结果

//  延迟输出 
//  加value参数做结果
const delay = (ms,value)=>{
    return new Promise((resolve, reject)=>{
        setTimeout(()=>{
            resolve(value)
        },ms)
    })
}

(async function demo(){
    let res = await delay(1000,{value:'梦想总是有的'});
    console.log('延迟输出',res);
})()

失败状态、增加随机时间功能、成功装状态、失败状态封装成一个方法

// --- 包含功能
//  延迟输出 
//  加value参数做结果
//  加失败 willResolve
//  成功、失败封装成一个函数
//  随机数封装一个函数   
//---


//  随机数
ramdomMs = (minNum, maxNum) => {
    return Math.floor(
        (maxNum - minNum) * Math.random() + 1 + minNum
    ) 
}

// 基本方法(成功,失败)
const createDelay = ({willResolve}) =>(ms,{value}={})=>{
    return new Promise((resolve, reject)=>{
        setTimeout(()=>{
            if(willResolve){
                resolve(`${value}:${ms}`)
            }else{
                reject(`${value}:${ms}`)
            }
        },ms)
    })
}

// 相关功能
const createWithTimers = ()=>{
    // 成功
    const delay = createDelay({willResolve:true})
    // 失败
    delay.reject = createDelay({willResolve:false})
    // 随机时间-成功的基础上增加
    delay.range = (minNum,maxNum,option)=>delay(ramdomMs(minNum,maxNum),option)

    return delay
}

const delay = createWithTimers()


async function demo(){
    let res = await delay(1000,{value:'梦想总是有的'});
    console.log('延迟输出:',res);

    try{
        await delay.reject(1000,{value:'失败是正常的'});
    }catch(err){
        console.log('err:',err)
    }
    
    let res2 =await delay.range(30,3000,{value:'你只管努力,其他的交给时间'})
    console.log('随机时间:',res2)
}

demo()

提前返回结果

// --- 包含功能
//  延迟输出 
//  加value参数做结果
//  加失败 willResolve
//  成功、失败封装成一个函数
//  随机数封装一个函数   
//  提前清除(提前返回结果)
//---


//  随机数
ramdomMs = (minNum, maxNum) => {
    return Math.floor(
        (maxNum - minNum) * Math.random() + 1 + minNum
    ) 
}

// 基本方法(成功,失败)
const createDelay = ({willResolve}) =>(ms,{value}={})=>{
    let timeoutId = null;
    let settle = null
    const delayPromise = new Promise((resolve, reject)=>{
        
        settle = ()=>{
            if(willResolve){
                resolve(`${value}:${ms}`)
            }else{
                reject(`${value}:${ms}`)
            }
        }

        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 = (minNum,maxNum,option)=>delay(ramdomMs(minNum,maxNum),option)

    return delay
}

const delay = createWithTimers()


async function demo(){
    let res3Promise = delay(3000,{value:'任何挫折都是纸老虎'})
    setTimeout(()=>{
        res3Promise.clear()
    },300)
    console.log(await res3Promise)
}

demo()

增加取消功能

// --- 包含功能
//  延迟输出 
//  加value参数做结果
//  加失败 willResolve
//  成功、失败封装成一个函数
//  随机数封装一个函数   
//  提前清除
//  取消功能
//       1.  添加监听事件  signal.addEventListener('abort', signalListener, {once: true})
//       2.  signalListener这个方法需要包含清除请求(清除倒计时),reject抛错
//       3.  settle执行,清除监听事件signal.removeEventListener('abort', signalListener)
//       4.  完善流程,初始化值的判段{aborted: false},建立报错信息
//---


// 建立报错信息
const createAbortError = () => {
    const error = new Error('Delay aborted');
    error.name = 'AbortError';
    return error;
};

//  随机数
ramdomMs = (minNum, maxNum) => {
    return Math.floor(
        (maxNum - minNum) * Math.random() + 1 + minNum
    ) 
}

// 基本方法(成功,失败)
const createDelay = ({willResolve}) =>(ms,{value, signal}={})=>{
    if (signal && signal.aborted) {
        return Promise.reject(createAbortError());
    }

    let timeoutId = null;
    let settle = null
    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}:${ms}`)
            }else{
                reject(`${value}:${ms}`)
            }
        }
        
        // 拿到reject的功能 
        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 = (minNum,maxNum,option)=>delay(ramdomMs(minNum,maxNum),option)

    return delay
}

const delay = createWithTimers()


async function demo(){

    const abortController = new AbortController();
    
    setTimeout(() => {
        abortController.abort();
    }, 5000);

    try{
        let res = await delay(10000,{value:'返回结果是我',signal: abortController.signal});
        console.log(res)
    }catch(err){
        console.log('err:',err)
    }
}

demo()

总结

AbortController
//AbortController 接口表示一个控制器对象,允许你根据需要终止 Web 请求。

// 属性
// - signal:{ 对事件进行标记
//     aborted:boolean // 可用于检测当前标记状态
// } 

// 方法
// - abort 终止事件执行

例: 终止fetch请求

fetchButton.onclick = async () => {
  const controller = new AbortController();

  // wire up abort button
  abortButton.onclick = () => controller.abort();

  try {
    const r = await fetch('/json', { signal: controller.signal });
    const json = await r.json();
    // TODO: do something 🤷
  } catch (e) {
    const isUserAbort = (e.name === 'AbortError');
    // TODO: show err 🧨
    // this will be a DOMException named AbortError if aborted 🎉
  }
};