diff options
Diffstat (limited to 'std/util')
-rw-r--r-- | std/util/async.ts | 117 | ||||
-rw-r--r-- | std/util/async_test.ts | 73 | ||||
-rw-r--r-- | std/util/deep_assign.ts | 31 | ||||
-rw-r--r-- | std/util/deep_assign_test.ts | 24 | ||||
-rw-r--r-- | std/util/has_own_property.ts | 30 |
5 files changed, 275 insertions, 0 deletions
diff --git a/std/util/async.ts b/std/util/async.ts new file mode 100644 index 000000000..6e2db69dc --- /dev/null +++ b/std/util/async.ts @@ -0,0 +1,117 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +// TODO(ry) It'd be better to make Deferred a class that inherits from +// Promise, rather than an interface. This is possible in ES2016, however +// typescript produces broken code when targeting ES5 code. +// See https://github.com/Microsoft/TypeScript/issues/15202 +// At the time of writing, the github issue is closed but the problem remains. +export interface Deferred<T> extends Promise<T> { + resolve: (value?: T | PromiseLike<T>) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reject: (reason?: any) => void; +} + +/** Creates a Promise with the `reject` and `resolve` functions + * placed as methods on the promise object itself. It allows you to do: + * + * const p = deferred<number>(); + * // ... + * p.resolve(42); + */ +export function deferred<T>(): Deferred<T> { + let methods; + const promise = new Promise<T>((resolve, reject): void => { + methods = { resolve, reject }; + }); + return Object.assign(promise, methods)! as Deferred<T>; +} + +interface TaggedYieldedValue<T> { + iterator: AsyncIterableIterator<T>; + value: T; +} + +/** The MuxAsyncIterator class multiplexes multiple async iterators into a + * single stream. It currently makes a few assumptions: + * - The iterators do not throw. + * - The final result (the value returned and not yielded from the iterator) + * does not matter; if there is any, it is discarded. + */ +export class MuxAsyncIterator<T> implements AsyncIterable<T> { + private iteratorCount = 0; + private yields: Array<TaggedYieldedValue<T>> = []; + private signal: Deferred<void> = deferred(); + + add(iterator: AsyncIterableIterator<T>): void { + ++this.iteratorCount; + this.callIteratorNext(iterator); + } + + private async callIteratorNext( + iterator: AsyncIterableIterator<T> + ): Promise<void> { + const { value, done } = await iterator.next(); + if (done) { + --this.iteratorCount; + } else { + this.yields.push({ iterator, value }); + } + this.signal.resolve(); + } + + async *iterate(): AsyncIterableIterator<T> { + while (this.iteratorCount > 0) { + // Sleep until any of the wrapped iterators yields. + await this.signal; + + // Note that while we're looping over `yields`, new items may be added. + for (let i = 0; i < this.yields.length; i++) { + const { iterator, value } = this.yields[i]; + yield value; + this.callIteratorNext(iterator); + } + + // Clear the `yields` list and reset the `signal` promise. + this.yields.length = 0; + this.signal = deferred(); + } + } + + [Symbol.asyncIterator](): AsyncIterableIterator<T> { + return this.iterate(); + } +} + +/** Collects all Uint8Arrays from an AsyncIterable and retuns a single + * Uint8Array with the concatenated contents of all the collected arrays. + */ +export async function collectUint8Arrays( + it: AsyncIterable<Uint8Array> +): Promise<Uint8Array> { + const chunks = []; + let length = 0; + for await (const chunk of it) { + chunks.push(chunk); + length += chunk.length; + } + if (chunks.length === 1) { + // No need to copy. + return chunks[0]; + } + const collected = new Uint8Array(length); + let offset = 0; + for (const chunk of chunks) { + collected.set(chunk, offset); + offset += chunk.length; + } + return collected; +} + +// Delays the given milliseconds and resolves. +export function delay(ms: number): Promise<void> { + return new Promise((res): number => + setTimeout((): void => { + res(); + }, ms) + ); +} diff --git a/std/util/async_test.ts b/std/util/async_test.ts new file mode 100644 index 000000000..adaac1e22 --- /dev/null +++ b/std/util/async_test.ts @@ -0,0 +1,73 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import { test, runIfMain } from "../testing/mod.ts"; +import { assert, assertEquals, assertStrictEq } from "../testing/asserts.ts"; +import { collectUint8Arrays, deferred, MuxAsyncIterator } from "./async.ts"; + +test(async function asyncDeferred(): Promise<void> { + const d = deferred<number>(); + d.resolve(12); +}); + +async function* gen123(): AsyncIterableIterator<number> { + yield 1; + yield 2; + yield 3; +} + +async function* gen456(): AsyncIterableIterator<number> { + yield 4; + yield 5; + yield 6; +} + +test(async function asyncMuxAsyncIterator(): Promise<void> { + const mux = new MuxAsyncIterator<number>(); + mux.add(gen123()); + mux.add(gen456()); + const results = new Set(); + for await (const value of mux) { + results.add(value); + } + assertEquals(results.size, 6); +}); + +test(async function collectUint8Arrays0(): Promise<void> { + async function* gen(): AsyncIterableIterator<Uint8Array> {} + const result = await collectUint8Arrays(gen()); + assert(result instanceof Uint8Array); + assertEquals(result.length, 0); +}); + +test(async function collectUint8Arrays0(): Promise<void> { + async function* gen(): AsyncIterableIterator<Uint8Array> {} + const result = await collectUint8Arrays(gen()); + assert(result instanceof Uint8Array); + assertStrictEq(result.length, 0); +}); + +test(async function collectUint8Arrays1(): Promise<void> { + const buf = new Uint8Array([1, 2, 3]); + async function* gen(): AsyncIterableIterator<Uint8Array> { + yield buf; + } + const result = await collectUint8Arrays(gen()); + assertStrictEq(result, buf); + assertStrictEq(result.length, 3); +}); + +test(async function collectUint8Arrays4(): Promise<void> { + async function* gen(): AsyncIterableIterator<Uint8Array> { + yield new Uint8Array([1, 2, 3]); + yield new Uint8Array([]); + yield new Uint8Array([4, 5]); + yield new Uint8Array([6]); + } + const result = await collectUint8Arrays(gen()); + assert(result instanceof Uint8Array); + assertStrictEq(result.length, 6); + for (let i = 0; i < 6; i++) { + assertStrictEq(result[i], i + 1); + } +}); + +runIfMain(import.meta); diff --git a/std/util/deep_assign.ts b/std/util/deep_assign.ts new file mode 100644 index 000000000..b1c9f9ac9 --- /dev/null +++ b/std/util/deep_assign.ts @@ -0,0 +1,31 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +export function deepAssign( + target: Record<string, unknown>, + ...sources: object[] +): object | undefined { + for (let i = 0; i < sources.length; i++) { + const source = sources[i]; + if (!source || typeof source !== `object`) { + return; + } + Object.entries(source).forEach(([key, value]: [string, unknown]): void => { + if (value instanceof Date) { + target[key] = new Date(value); + return; + } + if (!value || typeof value !== `object`) { + target[key] = value; + return; + } + if (Array.isArray(value)) { + target[key] = []; + } + // value is an Object + if (typeof target[key] !== `object` || !target[key]) { + target[key] = {}; + } + deepAssign(target[key] as Record<string, unknown>, value!); + }); + } + return target; +} diff --git a/std/util/deep_assign_test.ts b/std/util/deep_assign_test.ts new file mode 100644 index 000000000..c197344f7 --- /dev/null +++ b/std/util/deep_assign_test.ts @@ -0,0 +1,24 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import { test } from "../testing/mod.ts"; +import { assertEquals, assert } from "../testing/asserts.ts"; +import { deepAssign } from "./deep_assign.ts"; + +test(function deepAssignTest(): void { + const date = new Date("1979-05-27T07:32:00Z"); + const reg = RegExp(/DENOWOWO/); + const obj1 = { deno: { bar: { deno: ["is", "not", "node"] } } }; + const obj2 = { foo: { deno: date } }; + const obj3 = { foo: { bar: "deno" }, reg: reg }; + const actual = deepAssign(obj1, obj2, obj3); + const expected = { + foo: { + deno: new Date("1979-05-27T07:32:00Z"), + bar: "deno" + }, + deno: { bar: { deno: ["is", "not", "node"] } }, + reg: RegExp(/DENOWOWO/) + }; + assert(date !== expected.foo.deno); + assert(reg !== expected.reg); + assertEquals(actual, expected); +}); diff --git a/std/util/has_own_property.ts b/std/util/has_own_property.ts new file mode 100644 index 000000000..707d951d3 --- /dev/null +++ b/std/util/has_own_property.ts @@ -0,0 +1,30 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +/** + * Determines whether an object has a property with the specified name. + * Avoid calling prototype builtin `hasOwnProperty` for two reasons: + * + * 1. `hasOwnProperty` is defined on the object as something else: + * + * const options = { + * ending: 'utf8', + * hasOwnProperty: 'foo' + * }; + * options.hasOwnProperty('ending') // throws a TypeError + * + * 2. The object doesn't inherit from `Object.prototype`: + * + * const options = Object.create(null); + * options.ending = 'utf8'; + * options.hasOwnProperty('ending'); // throws a TypeError + * + * @param obj A Object. + * @param v A property name. + * @see https://eslint.org/docs/rules/no-prototype-builtins + */ +export function hasOwnProperty<T>(obj: T, v: PropertyKey): boolean { + if (obj == null) { + return false; + } + return Object.prototype.hasOwnProperty.call(obj, v); +} |