如何避免相同的请求

113 阅读2分钟

看到B站上一个视频,如何避免相同请求。页面上一些没有副作用且不会改变返回结果的请求是重复的,应该只请求一次,然后所有请求返回相同的结果。

视频里的做法是类似于发布订阅模式,数组记录回调函数,在异步函数执行完的时候,调用所有回调函数,然后缓存值。这种做法让我想起之前几天看到的 webpack 动态 import 文件的处理方式,创建一个 Promise ,记录 resolve,reject 函数,等到 jsonp 获取到数据再根据路径从 map 中调用 resolve 函数,继续执行。(不过这种做法很常见)

我和视频中的思路大体相同,但是实现方式不太一样,想验证一下自己的想法能否实现遂作如下代码。

我的思路就是只针对当前的请求,根据当前请求可能遇到的问题进行处理。 (可以看作我们根据闭包缓存了一些信息,视频缓存了 callabck 函数,而我缓存了请求的 promise )

const asyncOnce = (fn: () => Promise<unknown>, errorHandler: (err: Error) => void) => {
    // 如果参数有可能会变化,可以将下面三项变成 
    // record<string,{loading:boolean,pendingPromise:Promise<unknown>|null,result:unknown|null}>
    let loading = false
    let pendingPromise: Promise<unknown> | null = null
    let result: unknown | null = null
    return async () => {
        if (!loading && result) {
            // 有缓存
            return result
        }
        else if (loading) {
            // 没缓存,但是在请求
            await pendingPromise?.catch(errorHandler)
            return result
        } else {
            // 没缓存,没请求,直接发起请求
            return pendingPromise = new Promise((resolve, reject) => {
                loading = true
                fn().then((_res) => {
                    result = _res
                    loading = false
                    resolve(_res)
                }).catch(reject)
            })
        }
    }
}

const sleep = (period: number) => {
    return new Promise((resolve) => {
        setTimeout(resolve, period)
    })
}

const test = async () => {
    const period = 600
    console.log("test start waiting")
    await sleep(period)
    console.log("test has waited")
    return 123
}

const testOnce = asyncOnce(test, console.log)

const start = () => {
    testOnce().then((res) => {
        console.log("first res", res)
    })
    setTimeout(() => {
        testOnce().then((res) => {
            console.log("res in setTimeout", res)
        })
    }, 300)

    setTimeout(() => {
        testOnce().then((res) => {
            console.log("res in a long setTimeout", res)
        })
    }, 900)
}

start()