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) ...
我正在开发一个缓存文件的代理,我正在尝试添加一些逻辑来防止多个客户端在代理有机会缓存它们之前下载相同的文件。
基本上,我要实现的逻辑如下:
客户端 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) ...