javascript - Kokoa useita kutsuja ja erota tulos Promise:lla
Tällä hetkellä minulla on monta samanaikaista identtistä kutsua taustajärjestelmääni, jotka eroavat vain ID-kentän osalta:
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);
}
Tämä on tuhlailevaa, kun teen enemmän kutsuja. Haluaisin pitää getData()-kutsuni, mutta sitten yhdistää ne yhdeksi getDatas()-kutsuksi taustajärjestelmälleni ja palauttaa kaikki tulokset kutsujille. Minulla on enemmän hallintaa taustajärjestelmäni kuin käyttöliittymäkehyksen, joten voin helposti lisätä sille getDatas()-kutsun. Kysymys on, kuinka "mux" JS-kutsut yhdeksi taustakutsuksi ja "demux" tulos kutsujien promiseihin.
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 ???
}
Javassa loisinkin Map<int, CompletableFuture>-kartan, ja backend-pyynnön valmistuttua etsisin CompletableFuture:n ja kutsuisin sitä complete(data)-metodilla. Mutta mielestäni JS:ssä Promise:ja ei voi luoda ilman että niille annetaan eksplisiittinen tulos.
Voinko tehdä tämän JS:ssä lupauksilla?
Vastaus #1
Hieman epäselvää, miltä lopputavoitteesi näyttää. Kuvittelen, että voisit käydä läpi puhelusi tarpeen mukaan; Ehkä jotain tällaista:
for (let x in cache){
if (x.has(id))
return x;
}
//OR
for (let x=0; x<id.length;x++){
getData(id[x])
}
Voi toimia. Saatat pystyä lisäämään ajastimen menetelmän mukaan, jos tarpeen.
En ole varma, mistä taustajärjestelmäsi koostuu, mutta tiedän, että GraphQL on hyvä järjestelmä tekemään useita kutsuja.
Lopulta voi olla parempi käsitellä ne kaikki yhdessä pyynnössä sen sijaan, että tekisi useita kutsuja.
Vastaus #2
Välimuisti voi olla tavallinen objekti, joka yhdistää tunnukset lupauksen ratkaisuun liittyviin funktioihin ja lupaukseen, johon ne kuuluvat.
// cache maps ids to { resolve, reject, promise, requested }
// resolve and reject belong to the promise, requested is a bool for bookkeeping
const cache = {};
Saatat tarvita laukaisemista vain kerran, mutta tässä ehdotan setIntervalia tarkistamaan säännöllisesti välimuistia ratkaisemattomien pyyntöjen varalta:
// 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);
}
Liiketoimintalogiikka kutsuu vain getData(), joka vain luovuttaa (ja välimuistittaa) lupauksia, näin:
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;
}
Tallentamalla lupaus yhdessä ratkaisevan ja hylkäävän funktion kanssa toteutamme myös välimuistin, sillä ratkaistu lupaus tarjoaa sen, mihin se ratkaistiin, sen then() -metodin kautta.
getBatch() pyytää palvelimelta erässä vielä pyytämättömien getData()-tunnusten tiedot ja kutsuu vastaavat ratkaise/reject-funktiot:
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
})
})
}
Soittajan puoli näyttää tältä:
// 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);
