From 0355673a0f871b5d01838cc7838d269416769ac5 Mon Sep 17 00:00:00 2001 From: Simone Persiani Date: Mon, 16 Aug 2021 10:58:28 +0200 Subject: [PATCH] feat: Add function promiseSome Co-Authored-By: Ludovico Granata --- src/index.ts | 1 + src/util/PromiseUtil.ts | 30 +++++++++++++++++++++++++++++ test/unit/util/PromiseUtil.test.ts | 31 ++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/util/PromiseUtil.ts create mode 100644 test/unit/util/PromiseUtil.test.ts diff --git a/src/index.ts b/src/index.ts index 1a7a3f144..7bab76c46 100644 --- a/src/index.ts +++ b/src/index.ts @@ -325,6 +325,7 @@ export * from './util/FetchUtil'; export * from './util/GuardedStream'; export * from './util/HeaderUtil'; export * from './util/PathUtil'; +export * from './util/PromiseUtil'; export * from './util/QuadUtil'; export * from './util/RecordObject'; export * from './util/ResourceUtil'; diff --git a/src/util/PromiseUtil.ts b/src/util/PromiseUtil.ts new file mode 100644 index 000000000..7b72f82ee --- /dev/null +++ b/src/util/PromiseUtil.ts @@ -0,0 +1,30 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-function +const infinitePromise = new Promise((): void => {}); + +/** + * A function that simulates the Array.some behaviour but on an array of Promises. + * Returns true if at least one promise returns true. + * Returns false if all promises return false or error. + * + * @remarks + * + * Predicates provided as input must be implemented considering + * the following points: + * 1. if they throw an error, it won't be propagated; + * 2. throwing an error should be logically equivalent to returning false. + */ +export async function promiseSome(predicates: Promise[]): Promise { + // These promises will only finish when their predicate returns true + const infinitePredicates = predicates.map(async(predicate): Promise => predicate.then( + async(value): Promise => value ? true : infinitePromise, + async(): Promise => infinitePromise, + )); + + // Returns after all predicates are resolved + const finalPromise = Promise.allSettled(predicates).then((results): boolean => + results.some((result): boolean => result.status === 'fulfilled' && result.value)); + + // Either one of the infinitePredicates will return true, + // or finalPromise will return the result if none of them did or finalPromise was faster + return Promise.race([ ...infinitePredicates, finalPromise ]); +} diff --git a/test/unit/util/PromiseUtil.test.ts b/test/unit/util/PromiseUtil.test.ts new file mode 100644 index 000000000..e96a1113d --- /dev/null +++ b/test/unit/util/PromiseUtil.test.ts @@ -0,0 +1,31 @@ +import { promiseSome } from '../../../src/util/PromiseUtil'; + +describe('PromiseUtil', (): void => { + describe('#promiseSome', (): void => { + const resultTrue = Promise.resolve(true); + const resultFalse = Promise.resolve(false); + const resultError = Promise.reject(new Error('generic error')); + // eslint-disable-next-line @typescript-eslint/no-empty-function + const resultInfinite = new Promise((): void => {}); + + it('returns false if no promise is provided.', async(): Promise => { + await expect(promiseSome([])).resolves.toEqual(false); + }); + + it('returns false if no promise returns true.', async(): Promise => { + await expect(promiseSome([ resultFalse, resultFalse, resultFalse ])).resolves.toEqual(false); + }); + + it('returns true if at least a promise returns true.', async(): Promise => { + await expect(promiseSome([ resultFalse, resultTrue, resultFalse ])).resolves.toEqual(true); + }); + + it('does not propagate errors.', async(): Promise => { + await expect(promiseSome([ resultError, resultFalse, resultFalse ])).resolves.toEqual(false); + }); + + it('works with a combination of promises.', async(): Promise => { + await expect(promiseSome([ resultError, resultTrue, resultInfinite ])).resolves.toEqual(true); + }); + }); +});