diff --git a/packages/core/src/storages/dataset.ts b/packages/core/src/storages/dataset.ts index fe0528a96c57..95fcb046efce 100644 --- a/packages/core/src/storages/dataset.ts +++ b/packages/core/src/storages/dataset.ts @@ -508,31 +508,79 @@ export class Dataset { /** * Reduces a list of values down to a single value. * - * Memo is the initial state of the reduction, and each successive step of it should be returned by `iteratee()`. - * The `iteratee()` is passed three arguments: the `memo`, then the `value` and `index` of the iteration. + * The first element of the dataset is the initial value, with each successive reductions should + * be returned by `iteratee()`. The `iteratee()` is passed three arguments: the `memo`, `value` + * and `index` of the current element being folded into the reduction. * - * If no `memo` is passed to the initial invocation of reduce, the `iteratee()` is not invoked on the first element of the list. - * The first element is instead passed as the memo in the invocation of the `iteratee()` on the next element in the list. + * The `iteratee` is first invoked on the second element of the list (`index = 1`), with the + * first element given as the memo parameter. After that, the rest of the elements in the + * dataset is passed to `iteratee`, with the result of the previous invocation as the memo. + * + * If `iteratee()` returns a `Promise` it's awaited before a next call. + * + * If the dataset is empty, reduce will return undefined. + * + * @param iteratee + */ + async reduce(iteratee: DatasetReducer): Promise; + + /** + * Reduces a list of values down to a single value. + * + * The first element of the dataset is the initial value, with each successive reductions should + * be returned by `iteratee()`. The `iteratee()` is passed three arguments: the `memo`, `value` + * and `index` of the current element being folded into the reduction. + * + * The `iteratee` is first invoked on the second element of the list (`index = 1`), with the + * first element given as the memo parameter. After that, the rest of the elements in the + * dataset is passed to `iteratee`, with the result of the previous invocation as the memo. + * + * If `iteratee()` returns a `Promise` it's awaited before a next call. + * + * If the dataset is empty, reduce will return undefined. + * + * @param iteratee + * @param memo Unset parameter, neccesary to be able to pass options + * @param [options] An object containing extra options for `reduce()` + */ + async reduce( + iteratee: DatasetReducer, + memo: undefined, + options: DatasetIteratorOptions, + ): Promise; + + /** + * Reduces a list of values down to a single value. + * + * Memo is the initial state of the reduction, and each successive step of it should be returned + * by `iteratee()`. The `iteratee()` is passed three arguments: the `memo`, then the `value` and + * `index` of the iteration. * * If `iteratee()` returns a `Promise` then it's awaited before a next call. * * @param iteratee * @param memo Initial state of the reduction. - * @param [options] All `reduce()` parameters. + * @param [options] An object containing extra options for `reduce()` */ - async reduce(iteratee: DatasetReducer, memo: T, options: DatasetIteratorOptions = {}): Promise { + async reduce(iteratee: DatasetReducer, memo: T, options?: DatasetIteratorOptions): Promise; + + async reduce( + iteratee: DatasetReducer, + memo?: T, + options: DatasetIteratorOptions = {}, + ): Promise { checkStorageAccess(); - let currentMemo: T = memo; + let currentMemo: T | undefined = memo; const wrappedFunc: DatasetConsumer = async (item, index) => { - return Promise.resolve() - .then(() => { - return !index && currentMemo === undefined ? item : iteratee(currentMemo, item, index); - }) - .then((newMemo) => { - currentMemo = newMemo as T; - }); + if (index === 0 && currentMemo === undefined) { + currentMemo = item; + } else { + // We are guaranteed that currentMemo is instanciated, since we are either not on + // the first iteration, or memo was already set by the user. + currentMemo = await iteratee(currentMemo as T, item, index); + } }; await this.forEach(wrappedFunc, options); diff --git a/test/core/storages/dataset.test.ts b/test/core/storages/dataset.test.ts index 495ea68190f3..a00671c0600e 100644 --- a/test/core/storages/dataset.test.ts +++ b/test/core/storages/dataset.test.ts @@ -296,10 +296,9 @@ describe('dataset', () => { item.index = index; item.bar = 'xxx'; - // @ts-expect-error FIXME the inference is broken for `reduce()` method return memo.concat(item); }, - [], + [] as unknown[], { limit: 2, }, @@ -323,10 +322,9 @@ describe('dataset', () => { item.index = index; item.bar = 'xxx'; - // @ts-expect-error FIXME the inference is broken for `reduce()` method return Promise.resolve(memo.concat(item)); }, - [], + [] as unknown[], { limit: 2, }, @@ -369,10 +367,8 @@ describe('dataset', () => { const calledForIndexes: number[] = []; const result = await dataset.reduce( - // @ts-expect-error FIXME the inference is broken for `reduce()` method async (memo, item, index) => { calledForIndexes.push(index); - // @ts-expect-error FIXME the inference is broken for `reduce()` method return Promise.resolve(memo.foo > item.foo ? memo : item); }, undefined, @@ -391,8 +387,7 @@ describe('dataset', () => { offset: 2, }); - // @ts-expect-error FIXME the inference is broken for `reduce()` method - expect(result.foo).toBe(5); + expect(result!.foo).toBe(5); expect(calledForIndexes).toEqual([1, 2, 3]); }); });