聚合多个调用,然后使用Promise分离结果



目前,我对后端有许多并发的相同调用,只在ID字段上有所不同:

getData(1).then(...) // Each from a React component in a UI framework, so difficult to aggregate here
getData(2).then(...)
getData(3).then(...)
// creates n HTTP requests... inefficient
function getData(id: number): Promise<Data> {
return backend.getData(id);
}

这是浪费,因为我打了更多的电话。我想保留我的getData()调用,但随后将它们聚合为对后端的单个getDatas()调用,然后将所有结果返回给调用者。与UI框架相比,我对后端有更多的控制权,所以我可以很容易地在它上添加getDatas()调用;mux";JS调用为一个后端调用;去复用器";结果转化为调用者的承诺

const cache = Map<number, Promise<Data>>()
let requestedIds = []
let timeout = null;
// creates just 1 http request (per 100ms)... efficient!
function getData(id: number): Promise<Data> {
if (cache.has(id)) {
return cache;
}

requestedIds.push(id)
if (timeout == null) {
timeout = setTimeout(() => {
backend.getDatas(requestedIds).then((datas: Data[]) => {
// TODO: somehow populate many different promises in cache??? but how?
requestedIds = []
timeout = null
}
}, 100)
}
return ???
}

在Java中,我会创建一个Map<int, CompletableFuture>,在完成后端请求后,我会查找CompletableFuture并在其上调用complete(data)。但我认为在JS中,如果没有传递明确的结果,就无法创建Promise

我可以用Promises在JS中做到这一点吗?

对最终目标有点不清楚。我想你可以根据需要打电话;也许类似于:

for (let x in cache){
if (x.has(id))
return x;
} 
//OR
for (let x=0; x<id.length;x++){
getData(id[x])
}

也许行得通。如果需要,您可以在混合物中添加计时方法。

不确定您的后端由什么组成,但我知道GraphQL是一个用于进行多次调用的好系统。

最终可能最好在一个请求中处理所有这些请求,而不是多个调用。

缓存可以是一个常规对象,将id映射到promise解析函数及其所属的promise。

// cache maps ids to { resolve, reject, promise, requested }
// resolve and reject belong to the promise, requested is a bool for bookkeeping
const cache = {};

您可能只需要激发一次,但在这里我建议setInterval定期检查缓存中是否有未解决的请求:

// keep the return value, and stop polling with clearInterval()
// if you really only need one batch, change setInterval to setTimeout
function startGetBatch() {
return setInterval(getBatch, 100);
}

业务逻辑只调用getData(),它只是分发(并缓存(承诺,如下所示:

function getData(id) {
if (cache[id]) return cache[id].promise;
cache[id] = {};
const promise = new Promise((resolve, reject) => {
Object.assign(cache[id], { resolve, reject });
});
cache[id].promise = promise;
cache[id].requested = false;
return cache[id].promise;
}

通过将promise与解析器和rejecter一起保存,我们还实现了缓存,因为解析的promise将通过其then((方法提供它解析到的东西。

getBatch()批量向服务器请求尚未请求的getData()id,并调用相应的解析/拒绝函数:

function getBatch() {
// for any
const ids = [];
Object.keys(cache).forEach(id => {
if (!cache[id].requested) {
cache[id].requested = true;
ids.push(id);
}
});
return backend.getDatas(ids).then(datas => {
Object.keys(datas).forEach(id => {
cache[id].resolve(datas[id]);
})
}).catch(error => {
Object.keys(datas).forEach(id => {
cache[id].reject(error);
delete cache[id]; // so we can retry
})
})
}

呼叫方看起来是这样的:

// start polling 
const interval = startGetBatch();
// in the business logic
getData(5).then(result => console.log('the result of 5 is:', result));
getData(6).then(result => console.log('the result of 6 is:', result));
// sometime later...
getData(5).then(result => {
// if the promise for an id has resolved, then-ing it still works, resolving again to the -- now cached -- result
console.log('the result of 5 is:', result)
});
// later, whenever we're done 
// (no need for this if you change setInterval to setTimeout)
clearInterval(interval);

我想我找到了一个解决方案:

interface PromiseContainer {
resolve;
reject;
}
const requests: Map<number, PromiseContainer<Data>> = new Map();
let timeout: number | null = null;
function getData(id: number) {
const promise = new Promise<Data>((resolve, reject) => requests.set(id, { resolve, reject }))
if (timeout == null) {
timeout = setTimeout(() => {
backend.getDatas([...requests.keys()]).then(datas => {
for (let [id, data] of Object.entries(datas)) {
requests.get(Number(id)).resolve(data)
requests.delete(Number(id))
}
}).catch(e => {
Object.values(requests).map(promise => promise.reject(e))
})
timeout = null
}, 100)
}
return promise;
}

关键是要弄清楚我可以从promise中提取(resolve, reject),存储它们,然后检索并稍后调用它们。

最新更新