/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { CancellationTokenSource } from './cancellation.js'; import { canceled } from './errors.js'; import { toDisposable } from './lifecycle.js'; export function isThenable(obj) { return !!obj && typeof obj.then === 'function'; } export function createCancelablePromise(callback) { const source = new CancellationTokenSource(); const thenable = callback(source.token); const promise = new Promise((resolve, reject) => { const subscription = source.token.onCancellationRequested(() => { subscription.dispose(); source.dispose(); reject(canceled()); }); Promise.resolve(thenable).then(value => { subscription.dispose(); source.dispose(); resolve(value); }, err => { subscription.dispose(); source.dispose(); reject(err); }); }); return new class { cancel() { source.cancel(); } then(resolve, reject) { return promise.then(resolve, reject); } catch(reject) { return this.then(undefined, reject); } finally(onfinally) { return promise.finally(onfinally); } }; } export function raceCancellation(promise, token, defaultValue) { return Promise.race([promise, new Promise(resolve => token.onCancellationRequested(() => resolve(defaultValue)))]); } /** * A helper to prevent accumulation of sequential async tasks. * * Imagine a mail man with the sole task of delivering letters. As soon as * a letter submitted for delivery, he drives to the destination, delivers it * and returns to his base. Imagine that during the trip, N more letters were submitted. * When the mail man returns, he picks those N letters and delivers them all in a * single trip. Even though N+1 submissions occurred, only 2 deliveries were made. * * The throttler implements this via the queue() method, by providing it a task * factory. Following the example: * * const throttler = new Throttler(); * const letters = []; * * function deliver() { * const lettersToDeliver = letters; * letters = []; * return makeTheTrip(lettersToDeliver); * } * * function onLetterReceived(l) { * letters.push(l); * throttler.queue(deliver); * } */ export class Throttler { constructor() { this.activePromise = null; this.queuedPromise = null; this.queuedPromiseFactory = null; } queue(promiseFactory) { if (this.activePromise) { this.queuedPromiseFactory = promiseFactory; if (!this.queuedPromise) { const onComplete = () => { this.queuedPromise = null; const result = this.queue(this.queuedPromiseFactory); this.queuedPromiseFactory = null; return result; }; this.queuedPromise = new Promise(resolve => { this.activePromise.then(onComplete, onComplete).then(resolve); }); } return new Promise((resolve, reject) => { this.queuedPromise.then(resolve, reject); }); } this.activePromise = promiseFactory(); return new Promise((resolve, reject) => { this.activePromise.then((result) => { this.activePromise = null; resolve(result); }, (err) => { this.activePromise = null; reject(err); }); }); } } /** * A helper to delay (debounce) execution of a task that is being requested often. * * Following the throttler, now imagine the mail man wants to optimize the number of * trips proactively. The trip itself can be long, so he decides not to make the trip * as soon as a letter is submitted. Instead he waits a while, in case more * letters are submitted. After said waiting period, if no letters were submitted, he * decides to make the trip. Imagine that N more letters were submitted after the first * one, all within a short period of time between each other. Even though N+1 * submissions occurred, only 1 delivery was made. * * The delayer offers this behavior via the trigger() method, into which both the task * to be executed and the waiting period (delay) must be passed in as arguments. Following * the example: * * const delayer = new Delayer(WAITING_PERIOD); * const letters = []; * * function letterReceived(l) { * letters.push(l); * delayer.trigger(() => { return makeTheTrip(); }); * } */ export class Delayer { constructor(defaultDelay) { this.defaultDelay = defaultDelay; this.timeout = null; this.completionPromise = null; this.doResolve = null; this.doReject = null; this.task = null; } trigger(task, delay = this.defaultDelay) { this.task = task; this.cancelTimeout(); if (!this.completionPromise) { this.completionPromise = new Promise((resolve, reject) => { this.doResolve = resolve; this.doReject = reject; }).then(() => { this.completionPromise = null; this.doResolve = null; if (this.task) { const task = this.task; this.task = null; return task(); } return undefined; }); } this.timeout = setTimeout(() => { this.timeout = null; if (this.doResolve) { this.doResolve(null); } }, delay); return this.completionPromise; } isTriggered() { return this.timeout !== null; } cancel() { this.cancelTimeout(); if (this.completionPromise) { if (this.doReject) { this.doReject(canceled()); } this.completionPromise = null; } } cancelTimeout() { if (this.timeout !== null) { clearTimeout(this.timeout); this.timeout = null; } } dispose() { this.cancel(); } } /** * A helper to delay execution of a task that is being requested often, while * preventing accumulation of consecutive executions, while the task runs. * * The mail man is clever and waits for a certain amount of time, before going * out to deliver letters. While the mail man is going out, more letters arrive * and can only be delivered once he is back. Once he is back the mail man will * do one more trip to deliver the letters that have accumulated while he was out. */ export class ThrottledDelayer { constructor(defaultDelay) { this.delayer = new Delayer(defaultDelay); this.throttler = new Throttler(); } trigger(promiseFactory, delay) { return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay); } dispose() { this.delayer.dispose(); } } export function timeout(millis, token) { if (!token) { return createCancelablePromise(token => timeout(millis, token)); } return new Promise((resolve, reject) => { const handle = setTimeout(() => { disposable.dispose(); resolve(); }, millis); const disposable = token.onCancellationRequested(() => { clearTimeout(handle); disposable.dispose(); reject(canceled()); }); }); } export function disposableTimeout(handler, timeout = 0) { const timer = setTimeout(handler, timeout); return toDisposable(() => clearTimeout(timer)); } export function first(promiseFactories, shouldStop = t => !!t, defaultValue = null) { let index = 0; const len = promiseFactories.length; const loop = () => { if (index >= len) { return Promise.resolve(defaultValue); } const factory = promiseFactories[index++]; const promise = Promise.resolve(factory()); return promise.then(result => { if (shouldStop(result)) { return Promise.resolve(result); } return loop(); }); }; return loop(); } export class TimeoutTimer { constructor(runner, timeout) { this._token = -1; if (typeof runner === 'function' && typeof timeout === 'number') { this.setIfNotSet(runner, timeout); } } dispose() { this.cancel(); } cancel() { if (this._token !== -1) { clearTimeout(this._token); this._token = -1; } } cancelAndSet(runner, timeout) { this.cancel(); this._token = setTimeout(() => { this._token = -1; runner(); }, timeout); } setIfNotSet(runner, timeout) { if (this._token !== -1) { // timer is already set return; } this._token = setTimeout(() => { this._token = -1; runner(); }, timeout); } } export class IntervalTimer { constructor() { this._token = -1; } dispose() { this.cancel(); } cancel() { if (this._token !== -1) { clearInterval(this._token); this._token = -1; } } cancelAndSet(runner, interval) { this.cancel(); this._token = setInterval(() => { runner(); }, interval); } } export class RunOnceScheduler { constructor(runner, delay) { this.timeoutToken = -1; this.runner = runner; this.timeout = delay; this.timeoutHandler = this.onTimeout.bind(this); } /** * Dispose RunOnceScheduler */ dispose() { this.cancel(); this.runner = null; } /** * Cancel current scheduled runner (if any). */ cancel() { if (this.isScheduled()) { clearTimeout(this.timeoutToken); this.timeoutToken = -1; } } /** * Cancel previous runner (if any) & schedule a new runner. */ schedule(delay = this.timeout) { this.cancel(); this.timeoutToken = setTimeout(this.timeoutHandler, delay); } get delay() { return this.timeout; } set delay(value) { this.timeout = value; } /** * Returns true if scheduled. */ isScheduled() { return this.timeoutToken !== -1; } onTimeout() { this.timeoutToken = -1; if (this.runner) { this.doRun(); } } doRun() { if (this.runner) { this.runner(); } } } /** * Execute the callback the next time the browser is idle */ export let runWhenIdle; (function () { if (typeof requestIdleCallback !== 'function' || typeof cancelIdleCallback !== 'function') { const dummyIdle = Object.freeze({ didTimeout: true, timeRemaining() { return 15; } }); runWhenIdle = (runner) => { const handle = setTimeout(() => runner(dummyIdle)); let disposed = false; return { dispose() { if (disposed) { return; } disposed = true; clearTimeout(handle); } }; }; } else { runWhenIdle = (runner, timeout) => { const handle = requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined); let disposed = false; return { dispose() { if (disposed) { return; } disposed = true; cancelIdleCallback(handle); } }; }; } })(); /** * An implementation of the "idle-until-urgent"-strategy as introduced * here: https://philipwalton.com/articles/idle-until-urgent/ */ export class IdleValue { constructor(executor) { this._didRun = false; this._executor = () => { try { this._value = executor(); } catch (err) { this._error = err; } finally { this._didRun = true; } }; this._handle = runWhenIdle(() => this._executor()); } dispose() { this._handle.dispose(); } get value() { if (!this._didRun) { this._handle.dispose(); this._executor(); } if (this._error) { throw this._error; } return this._value; } get isInitialized() { return this._didRun; } } //#endregion //#region Promises export var Promises; (function (Promises) { /** * A drop-in replacement for `Promise.all` with the only difference * that the method awaits every promise to either fulfill or reject. * * Similar to `Promise.all`, only the first error will be returned * if any. */ function settled(promises) { return __awaiter(this, void 0, void 0, function* () { let firstError = undefined; const result = yield Promise.all(promises.map(promise => promise.then(value => value, error => { if (!firstError) { firstError = error; } return undefined; // do not rethrow so that other promises can settle }))); if (typeof firstError !== 'undefined') { throw firstError; } return result; // cast is needed and protected by the `throw` above }); } Promises.settled = settled; })(Promises || (Promises = {})); //#endregion