The ideas are
- When multiple async calls are made, to be able to start consuming from the first resolving one regardless in what order the promises are fired.
- To construct a modern emitter of async values to be consumed by the
for await
loop. - One may achieve this functionality by making repeated (promise many times)
Promise.race()
s but i want to believe that no good programmer would feel comfortable with that. - Because both it's wasteful and i never liked
Promise.race()
. It's a misnomer. I mean where in the world there is a race that finalizes once somebody wins? It's an insult. It's like calling the second one "Hey.. you are at the top of all losers". Let's change that.
One implementation of this functionality could be starting to render a multipart entity earlier than otherwise. There might be other use cases too, since this will not reject altogether even if a promise in the system gets somehow rejected. We can easily handle rejections in an isolated manner, separatelly, perhaps for retrying or whatnot. Also the promises can be identified as they resolve and we can tell where to use their resolution.
The code below is just a skeleton.
- It just shows the mechanism with a simple exception handling.
- It doesn't include any retry or promise identification abilities.
- It doesn't remove the consumed (fulfilled) promise from the structure. This can be iplemented in the
finally
block. Once it's implemented, thecount
variable will be redundant and we can then use the dynamiclength
property instead. - Being able to use the dynamic
length
property would allow us to add, remove or replace promises at any point in time just by using standard array methods like.push()
or.splice()
etc. - It uses the new Private Class Fields which i like a lot.
We will start with a new data type called SortedPromisesArray
which in fact is an extension to the Array
type. We will empower it with generator and asyncIterator
abilities.
export default class SortedPromisesArray extends Array {
#resolve;
#reject;
#count;
constructor(...args){
super(...args.filter(p => Object(p).constructor === Promise)); // Make sure everybody is a Promise
this.#count = this.length;
this.forEach(p => p.then(v => this.#resolve(v), e => this.#reject(e)));
};
async *[Symbol.asyncIterator]() {
while(this.#count--) {
try {
yield new Promise((...rs) => [this.#resolve,this.#reject] = rs);
}
catch(e){
console.log(`Caught an exception ${e}`);
}
finally{
// a handy stage to do useful things
};
};
};
};
and we can consume it like;
import SPA from "../lib/promise-sort.js";
var promise = (val,delay,isOK) => new Promise((v,x) => setTimeout(_ => isOK ? v(val) : x(val), delay)),
promises = [ promise("Third", 3000, true)
, promise("First", 1000, true)
, Promise.resolve("this is solved first @ microtask queue")
, promise("Second", 2000, false) // NOTE: this one rejects!
],
sortedPS = new SPA(...promises);
async function sink() {
for await (let value of sortedPS){
console.log(`Got: ${value}`);
};
};
sink();
Any thoughts and recommendations are most welcome.