如何实现一个可取消的promise?

1,373 阅读2分钟

ES6的promise实现是很可靠的,但是仍然有不足之处,比方说它没有实现取消promise的功能。这导致,只要promise的逻辑开始执行,就没有办法阻止,只能干等着它执行完成。
而很多时候,我们会碰到promise正在处理中,但是我们已经不需要/不关心它的结果的情况。比方说,我们在组件mount完之后发送ajax请求获取页面数据,并交予相关回调处理,但是我们在组件unmount的时候该ajax请求还没有返回结果,此时就需要取消这个promise,让程序不再执行后续的处理逻辑。
ES6没有为我们提供原生的cancel方法,我们需要手动实现。这里我的实现基于对promise的二次封装,以及Promise类的静态方法Promise.race。

/**
 *  将原始promise包装成带cancel方法的promise
 *  @param Promise 原始的promise
 *  @return Promise 包装后的promise
 * */
function withCancel(originalPromise){
    let cancel=()=>{}
    let isCancelled=false
    
    // 辅助的promise,在调用cancel之后该promise会立即reject
    const cancelPromise=new Promise((resolve, reject) => {
        cancel=e=>{
            isCancelled=true
            reject(e)
        }
    })

    // 包装后的promise,本质是Promise.race的返回值,Promise.race传参是原始的promise和辅助的promise
    const groupPromise=Promise.race([originalPromise,cancelPromise])
        .catch(e=>{
            // isCancelled标志位,表明用户是否主动触发cancel。如果是主动触发,不要抛出异常
            if(isCancelled){
                console.log('promise is cancelled')
                console.log(e)
                return new Promise(()=>{})
            }
            else return Promise.reject(e)
        })

    return Object.assign(groupPromise, {cancel})
}

使用方法如下,在该例子中,原始promise会在3秒后被resolve,并且在then中会打印5。但是我在1.5秒的时候调用了cancel方法,这样原始promise的then中的逻辑就不会执行:

const originalPromise=new Promise((resolve, reject) => {
    setTimeout(()=>resolve(5),3000)
})

const promiseWithCancel=withCancel(originalPromise)

promiseWithCancel.then(console.log)

setTimeout(()=>promiseWithCancel.cancel('Hi, this is a cancel message'),1500)

输出如下:

promise is cancelled
Hi, this is a cancel message