Skip to content

Commit

Permalink
Merge pull request #6797 from Agoric/mfig-internal-iterables
Browse files Browse the repository at this point in the history
feat(internal): iterable produces values and can be async
  • Loading branch information
mergify[bot] authored Jan 24, 2023
2 parents bc4e1aa + 8edeb04 commit e3a7775
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 23 deletions.
62 changes: 39 additions & 23 deletions packages/internal/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,37 +411,53 @@ export const assertAllDefined = obj => {
}
};

const neverDone = harden({ done: false, value: null });
/** @type {IteratorResult<undefined, never>} */
const notDone = harden({ done: false, value: undefined });

/** @type {AsyncIterable<null>} */
export const forever = asyncGenerate(() => neverDone);
/** @type {IteratorResult<never, void>} */
const alwaysDone = harden({ done: true, value: undefined });

export const forever = asyncGenerate(() => notDone);

/**
* @param {() => unknown} boolFunc
* `boolFunc`'s return value is used for its truthiness vs falsiness.
* @template T
* @param {() => T} produce
* The value of `await produce()` is used for its truthiness vs falsiness.
* IOW, it is coerced to a boolean so the caller need not bother doing this
* themselves.
* @returns {AsyncIterable<null>}
* @returns {AsyncIterable<Awaited<T>>}
*/
export const whileTrue = boolFunc =>
asyncGenerate(() =>
harden({
done: !boolFunc(),
value: null,
}),
);
export const whileTrue = produce =>
asyncGenerate(async () => {
const value = await produce();
if (!value) {
return alwaysDone;
}
return harden({
done: false,
value,
});
});

/**
* @param {() => unknown} boolFunc
* `boolFunc`'s return value is used for its truthiness vs falsiness.
* @template T
* @param {() => T} produce
* The value of `await produce()` is used for its truthiness vs falsiness.
* IOW, it is coerced to a boolean so the caller need not bother doing this
* themselves.
* @returns {AsyncIterable<null>}
* @returns {AsyncIterable<Awaited<T>>}
*/
export const untilTrue = boolFunc =>
asyncGenerate(() =>
harden({
done: !!boolFunc(),
value: null,
}),
);
export const untilTrue = produce =>
asyncGenerate(async () => {
const value = await produce();
if (value) {
return harden({
done: true,
value,
});
}
return harden({
done: false,
value,
});
});
49 changes: 49 additions & 0 deletions packages/internal/test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {
objectMap,
makeMeasureSeconds,
assertAllDefined,
whileTrue,
untilTrue,
forever,
} from '../src/utils.js';

test('fromUniqueEntries', t => {
Expand Down Expand Up @@ -92,3 +95,49 @@ test('assertAllDefined', t => {
foo.prop.toFixed,
);
});

test('whileTrue', async t => {
const elems = [1, 2, 3];
/** @type {any} */
let cur;
let count = 0;
for await (const produced of whileTrue(() => {
cur = elems.shift();
count += 1;
return count % 2 === 0 ? cur : Promise.resolve(cur);
})) {
t.is(produced, cur, 'cur is produced');
t.not(produced, undefined, 'produced is not undefined');
}
t.is(cur, undefined, 'cur is done');
t.is(count, 4, 'count is expected');
});

test('untilTrue', async t => {
const elems = [0, false, null, NaN, '', 'truey', 1.39];
/** @type {any} */
let cur;
let count = 0;
for await (const produced of untilTrue(() => {
cur = elems.shift();
count += 1;
return count % 2 === 0 ? cur : Promise.resolve(cur);
})) {
t.is(produced, cur, 'cur is produced');
t.assert(!produced, 'produced is falsy');
}
t.is(cur, 'truey', 'cur is done');
t.is(count, 6, 'count is expected');
});

test('forever', async t => {
let count = 0;
for await (const produced of forever) {
t.is(produced, undefined, 'produced is undefined');
count += 1;
if (count > 3) {
break;
}
}
t.is(count, 4, 'count is expected');
});

0 comments on commit e3a7775

Please sign in to comment.