Javascript/Nodejs Promise 多次延迟

Multiple delays in Javascript/Nodejs Promise

我正在开发一个缓存文件的代理,我正在尝试添加一些逻辑来防止多个客户端在代理有机会缓存​​它们之前下载相同的文件。

基本上,我要实现的逻辑如下:

客户端 1 请求一个文件。代理检查文件是否被缓存。如果不是,它从服务器请求它,缓存它,然后将它发送给客户端。

客户端 2 在客户端 1 请求之后请求相同的文件,但在代理有机会缓存​​它之前。所以代理会告诉客户端 2 等待几秒钟,因为已经有一个下载在进行中。

更好的方法可能是向客户端 2 发送一条 "try again later" 消息,但我们只能说这目前不是一种选择。

我在 anyproxy library. According to the documentation 中使用 Nodejs,使用 promises 可以延迟响应。

但是,我真的没有找到使用 Promises 实现我想要的东西的方法。据我所知,我可以做这样的事情:

module.exports = {
  *beforeSendRequest(requestDetail) {
    if(thereIsADownloadInProgressFor(requestDetail.url)) {
        return new Promise((resolve, reject) => {
            setTimeout(() => { // delay
              resolve({ response: responseDetail.response });
            }, 10000);
        });
    }
  }
};

但这意味着只需等待最长的时间并希望到那时下载完成。 我不想这样。

我更希望能够做这样的事情(但以某种方式使用 Promises):

module.exports = {
  *beforeSendRequest(requestDetail) {
    if(thereIsADownloadInProgressFor(requestDetail.url)) {
        var i = 0;
        for(i = 0 ; i < 10 ; i++) {
            JustSleep(1000);

            if(!thereIsADownloadInProgressFor(requestDetail.url))
                return { response: responseDetail.response };
        }
    }
  }
};

有什么方法可以通过 Nodejs 中的 Promises 实现吗?

谢谢!

您可以使用 Map 来缓存您的文件下载。

Map 中的映射为 url -> Promise { file }

// Map { url => Promise { file } }
const cache = new Map()

const thereIsADownloadInProgressFor = url => cache.has(url)

const getCachedFilePromise = url => cache.get(url)

const downloadFile = async url => {/* download file code here */}

const setAndReturnCachedFilePromise = url => {
  const filePromise = downloadFile(url)
  cache.set(url, filePromise)
  return filePromise
}

module.exports = {
  beforeSendRequest(requestDetail) {
    if(thereIsADownloadInProgressFor(requestDetail.url)) {
        return getCachedFilePromise(requestDetail.url).then(file => ({ response: file }))
    } else {
        return setAndReturnCachedFilePromise(requestDetail.url).then(file => ({ response: file }))
    }
  }
};

您无需发送重试响应,只需向两个请求提供相同的数据即可。您需要做的就是将请求存储在缓存系统中的某个位置,并在获取完成后触发所有请求。

这是一个缓存实现,它只为多个请求执行一次提取。没有延迟,也没有再试:

export class class Cache {    
    constructor() {
        this.resultCache = {}; // this object is the cache storage
    }

    async get(key, cachedFunction) {
        let cached = this.resultCache[key];

        if (cached === undefined) { // No cache so fetch data
            this.resultCache[key] = {
                pending: [] // This is the magic, store further
                            // requests in this pending array.
                            // This way pending requests are directly
                            // linked to this cache data
            }
            try {
                let result = await cachedFunction(); // Wait for result

                // Once we get result we need to resolve all pending
                // promises. Loop through the pending array and
                // resolve them. See code below for how we store pending
                // requests.. it will make sense:
                this.resultCache[key].pending
                    .forEach(waiter => waiter.resolve(result));

                // Store the result of the cache so later we don't
                // have to fetch it again:
                this.resultCache[key] = {
                    data: result
                }

                // Return result to original promise:
                return result;

                // Note: yes, this means pending promises will get triggered
                // before the original promise is resolved but normally
                // this does not matter. You will need to modify the
                // logic if you want promises to resolve in original order
            }
            catch (err) { // Error when fetching result

                // We still need to trigger all pending promises to tell
                // them about the error. Only we reject them instead of
                // resolving them:
                if (this.resultCache[key]) {
                    this.resultCache[key].pending
                      .forEach((waiter: any) => waiter.reject(err));
                }
                throw err;
            }
        }
        else if (cached.data === undefined && cached.pending !== undefined) {
            // Here's the condition where there was a previous request for
            // the same data. Instead of fetching the data again we store
            // this request in the existing pending array.
            let wait = new Promise((resolve, reject) => {

                // This is the "waiter" object above. It is basically
                // It is basically the resolve and reject functions
                // of this promise:
                cached.pending.push({
                    resolve: resolve,
                    reject: reject
                });
            });

            return await wait; // await response form original request.
                               // The code above will cause this to return.
        }
        else {
            // Return cached data as normal
            return cached.data;
        }
    }
}

代码看起来有点复杂,其实很简单。首先我们需要一种方法来存储缓存数据。通常我会为此使用一个常规对象:

{ key : result }

其中缓存数据存储在result中。但是我们还需要存储额外的元数据,例如对相同结果的未决请求。所以我们需要修改我们的缓存存储:

{ key : {
    data: result,
    pending: [ array of requests ]
  }
}

所有这些对于使用此缓存的代码都是不可见和透明的 class。

用法:

const cache = new Cache();

// Illustrated with w3c fetch API but you may use anything:
cache.get( URL , () => fetch(URL) )

请注意,将提取包装在匿名函数中很重要,因为我们希望 Cache.get() 函数有条件地调用提取以避免调用多个提取。它还提供了 Cache class 处理任何类型的异步操作的灵活性。

这是缓存 setTimeout 的另一个示例。它不是很有用,但它说明了 API:

的灵​​活性
cache.get( 'example' , () => {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, 1000);
    });
});

请注意,为清楚起见,上面的 Cache class 没有任何失效或过期逻辑,但添加它们相当容易。例如,如果您希望缓存在一段时间后过期,您可以将时间戳与其他缓存数据一起存储:

{ key : {
    data: result,
    timestamp: timestamp,
    pending: [ array of requests ]
  }
}

然后在"no-cache"逻辑中简单检测到期时间:

if (cached === undefined || (cached.timestamp + timeout) < now) ...