目录

Promise提前结束

目录
警告
本文最后更新于 2022-09-20,文中内容可能已过时。

常见的需求是,当点击一个按钮时,连续点击多次,就会触发多次请求,我们可以通过防抖来解决,也可以通过取消相同的重复请求来实现。

axios 已经有了取消请求的功能:

已废弃

const CancelToken = axios.CancelToken
let cancel

axios.get('/demo', {
    cancelToken: new CancelToken(c => {
        cancel = c
    })
})

cancel() // 取消这个请求

/* ---------- 或者 ---------- */
const CancelToken = axios.CancelToken
const source = CancelToken.source()

axios
    .get('/user/12345', {
        cancelToken: source.token
    })
    .catch(function (thrown) {
        if (axios.isCancel(thrown)) {
            console.log('Request canceled', thrown.message)
        } else {
            // 处理错误
        }
    })

axios.post(
    '/user/12345',
    {
        name: 'new name'
    },
    {
        cancelToken: source.token
    }
)

// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.')

上方的 CancelToken 从 v0.22.0 版本被废弃,最新的 axios 使用的是与 fetch 一样的 api AbortController:

js

const controller = new AbortController()

axios
    .get('/foo/bar', {
        signal: controller.signal
    })
    .then(function (response) {
        //...
    })
// 取消请求
controller.abort()

还是那句话,我们主要学习思想嘛,针对一个 promise,怎么做到"中断" promise 的执行呢?一起来看看。

首先我在"中断"上打了引号,因为 promise 一旦创建是无法取消的,所谓的”中断“,只是在合适的时候,提前结束 pending 状态而已,所以本文题目才是提前结束而不是“中断”。

js

const mockReq = function (time) {
    const req = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(time)
        }, time)
    })
    let abort = null
    const stopHandler = new Promise((resolve, reject) => {
        abort = reject
    })
    const p = Promise.race([req, stopHandler])
    p.abort = abort
    return p
}

mockReq(2000).then(res => console.log(res))
mockReq().abort()

其实核心就是借助另一个永远不会成功或失败的 promise 的 execute 函数,来让人可以手动改变 handle 的状态,进而影响 Promise.race 的执行。值得注意的是,req 仍然会执行完成,但是已经无意义了。

promise 超时控制原理一样,把一个设定超时时间的 promise 和正常的请求放到 Promise.race()中即可。