Wrapper over Promise.race([...promises])
to identify which condition was met.
Method signature interface is identical to Redux Saga's race({...promiseMap})
method, but without Redux Saga as a requirement. Straight up plain JavaScript async...await
without dependencies. In TypeScript, return type is also identified according to object key provided per Promise instance (in Redux Saga, a key is described as a "label"), so, there is clear indication of which key maps to which expected type of return value.
Method signature:
race({
[key: string]: Promise<any>
}) => Promise<{
[Key in keyof PromiseRecord]?: Awaited<PromiseRecord[Key]>;
}>;
- Each
key
you add is a label to a promise (Promise<any>
). - An object, if successful, the
key
("label") and the result of the associated promise that completed first. - An error, if unsuccessful, from the promise which has thrown it. Ideally, the error should be an
Error
instance.
Like the overview says, you want to know which one of the race conditions was met first, in plain JavaScript. The Promise.race()
method does indicate that one of the conditions wins the race, but it doesn't tell you which one it was.
With proper support for the modern JavaScript syntax, there is simply no need to use a more powerful library such as Redux Saga. If your project is not sophisticated enough to demand the rest of Redux Saga's features, then all you would need are simple method wrappers over the JavaScript API, such as this race()
method.
const promise1 = new Promise<'one'>((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise<'two'>((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
const promise3 = new Promise<'three'>((resolve, reject) => {
setTimeout(resolve, 1000, 'three');
});
const promise4 = new Promise<'four'>((resolve, reject) => {
setTimeout(resolve, 5000, 'four');
});
async function main() {
const { p1, p2, p3, p4 } = await race({
p1: promise1,
p2: promise2,
p3: promise3,
p4: promise4,
});
if (p1) {
console.log('p1', p1);
} else if (p2) {
console.log('p2', p2);
} else if (p3) {
console.log('p3', p3);
} else if (p4) {
console.log('p4', p4);
}
}
main();
Output in console:
p2 two
Note: In Redux Saga, when an error is caught and is the first to complete the race()
, the condition that caused this error is not identified. It is the reponsibility of the implementation detail to properly identify where exactly that error occurred.
const promise1 = new Promise<'one'>((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise<'two'>((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
const promise3 = new Promise<'three'>((resolve, reject) => {
setTimeout(resolve, 1000, 'three');
});
const promise4 = new Promise<'four'>((resolve, reject) => {
reject(new Error('p4 hit an error'));
});
async function main() {
try {
const { p1, p2, p3, p4 } = await race({
p1: promise1,
p2: promise2,
p3: promise3,
p4: promise4,
});
if (p1) {
console.log('p1', p1);
} else if (p2) {
console.log('p2', p2);
} else if (p3) {
console.log('p3', p3);
} else if (p4) {
console.log('p4', p4);
} else {
console.log('???', { p1, p2, p3, p4 });
}
} catch (error) {
console.log('error', error);
}
}
main();
Output in console:
error Error: p4 hit an error
at eval (eval at <anonymous> (runtime.ts:153:7), <anonymous>:29:12)
at new Promise (<anonymous>)
at eval (eval at <anonymous> (runtime.ts:153:7), <anonymous>:28:18)
at runtime.ts:153:7
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race JavaScript
Promise.race()
API - https://redux-saga.js.org/docs/api/#raceeffects Redux Saga
race()
API, specifically the variant which accepts labeled race conditions - https://stackoverflow.com/a/51659490 Inferring return types mapped according to object key
- https://stackoverflow.com/a/57364353 TypeScript
Awaited
keyword to infer the return type of aPromise
object.