async.ts 19.0 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
J
Joao Moreno 已提交
5 6

import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
J
Johannes Rieken 已提交
7 8
import * as errors from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
J
Joao Moreno 已提交
9
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
10
import { URI } from 'vs/base/common/uri';
11

J
Johannes Rieken 已提交
12 13
export function isThenable<T>(obj: any): obj is Promise<T> {
	return obj && typeof (<Promise<any>>obj).then === 'function';
14 15
}

16 17 18 19
export interface CancelablePromise<T> extends Promise<T> {
	cancel(): void;
}

J
Johannes Rieken 已提交
20
export function createCancelablePromise<T>(callback: (token: CancellationToken) => Promise<T>): CancelablePromise<T> {
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
	const source = new CancellationTokenSource();

	const thenable = callback(source.token);
	const promise = new Promise<T>((resolve, reject) => {
		source.token.onCancellationRequested(() => {
			reject(errors.canceled());
		});
		Promise.resolve(thenable).then(value => {
			source.dispose();
			resolve(value);
		}, err => {
			source.dispose();
			reject(err);
		});
	});

	return new class implements CancelablePromise<T> {
		cancel() {
			source.cancel();
		}
J
Johannes Rieken 已提交
41
		then<TResult1 = T, TResult2 = never>(resolve?: ((value: T) => TResult1 | Promise<TResult1>) | undefined | null, reject?: ((reason: any) => TResult2 | Promise<TResult2>) | undefined | null): Promise<TResult1 | TResult2> {
42 43
			return promise.then(resolve, reject);
		}
J
Johannes Rieken 已提交
44
		catch<TResult = never>(reject?: ((reason: any) => TResult | Promise<TResult>) | undefined | null): Promise<T | TResult> {
45 46
			return this.then(undefined, reject);
		}
47
		finally(onfinally?: (() => void) | undefined | null): Promise<T> {
48
			return always(promise, onfinally);
49
		}
50 51 52
	};
}

53
export function asPromise<T>(callback: () => T | Thenable<T>): Promise<T> {
54
	return new Promise<T>((resolve, reject) => {
55
		let item = callback();
J
Johannes Rieken 已提交
56
		if (isThenable<T>(item)) {
57 58 59 60
			item.then(resolve, reject);
		} else {
			resolve(item);
		}
61
	});
62 63
}

E
Erich Gamma 已提交
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
export interface ITask<T> {
	(): T;
}

/**
 * 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:
 *
B
Benjamin Pasero 已提交
80 81
 * 		const throttler = new Throttler();
 * 		const letters = [];
E
Erich Gamma 已提交
82
 *
J
Joao Moreno 已提交
83 84 85 86 87 88 89
 * 		function deliver() {
 * 			const lettersToDeliver = letters;
 * 			letters = [];
 * 			return makeTheTrip(lettersToDeliver);
 * 		}
 *
 * 		function onLetterReceived(l) {
E
Erich Gamma 已提交
90
 * 			letters.push(l);
J
Joao Moreno 已提交
91
 * 			throttler.queue(deliver);
E
Erich Gamma 已提交
92 93 94 95
 * 		}
 */
export class Throttler {

J
Johannes Rieken 已提交
96 97 98
	private activePromise: Promise<any> | null;
	private queuedPromise: Promise<any> | null;
	private queuedPromiseFactory: ITask<Promise<any>> | null;
E
Erich Gamma 已提交
99 100 101 102 103 104 105

	constructor() {
		this.activePromise = null;
		this.queuedPromise = null;
		this.queuedPromiseFactory = null;
	}

J
Johannes Rieken 已提交
106
	queue<T>(promiseFactory: ITask<Promise<T>>): Promise<T> {
E
Erich Gamma 已提交
107 108 109 110
		if (this.activePromise) {
			this.queuedPromiseFactory = promiseFactory;

			if (!this.queuedPromise) {
J
Johannes Rieken 已提交
111
				const onComplete = () => {
E
Erich Gamma 已提交
112 113
					this.queuedPromise = null;

A
Alex Dima 已提交
114
					const result = this.queue(this.queuedPromiseFactory!);
E
Erich Gamma 已提交
115 116 117 118 119
					this.queuedPromiseFactory = null;

					return result;
				};

J
Joao Moreno 已提交
120
				this.queuedPromise = new Promise(c => {
A
Alex Dima 已提交
121
					this.activePromise!.then(onComplete, onComplete).then(c);
E
Erich Gamma 已提交
122 123 124
				});
			}

J
Joao Moreno 已提交
125
			return new Promise((c, e) => {
A
Alex Dima 已提交
126
				this.queuedPromise!.then(c, e);
E
Erich Gamma 已提交
127 128 129 130 131
			});
		}

		this.activePromise = promiseFactory();

J
Joao Moreno 已提交
132
		return new Promise((c, e) => {
A
Alex Dima 已提交
133
			this.activePromise!.then((result: any) => {
E
Erich Gamma 已提交
134 135 136 137 138
				this.activePromise = null;
				c(result);
			}, (err: any) => {
				this.activePromise = null;
				e(err);
J
Joao Moreno 已提交
139
			});
E
Erich Gamma 已提交
140 141 142 143
		});
	}
}

J
Joao Moreno 已提交
144
export class Sequencer {
145

J
Joao Moreno 已提交
146
	private current: Promise<any> = Promise.resolve(null);
147

J
Johannes Rieken 已提交
148
	queue<T>(promiseTask: ITask<Promise<T>>): Promise<T> {
149 150 151 152
		return this.current = this.current.then(() => promiseTask());
	}
}

E
Erich Gamma 已提交
153 154 155 156
/**
 * A helper to delay execution of a task that is being requested often.
 *
 * Following the throttler, now imagine the mail man wants to optimize the number of
157
 * trips proactively. The trip itself can be long, so he decides not to make the trip
E
Erich Gamma 已提交
158 159 160 161 162 163 164 165 166 167
 * 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:
 *
B
Benjamin Pasero 已提交
168 169
 * 		const delayer = new Delayer(WAITING_PERIOD);
 * 		const letters = [];
E
Erich Gamma 已提交
170 171 172 173 174 175
 *
 * 		function letterReceived(l) {
 * 			letters.push(l);
 * 			delayer.trigger(() => { return makeTheTrip(); });
 * 		}
 */
176
export class Delayer<T> implements IDisposable {
E
Erich Gamma 已提交
177

178
	private timeout: any;
J
Johannes Rieken 已提交
179 180
	private completionPromise: Promise<any> | null;
	private doResolve: ((value?: any | Promise<any>) => void) | null;
181
	private doReject: (err: any) => void;
J
Johannes Rieken 已提交
182
	private task: ITask<T | Promise<T>> | null;
E
Erich Gamma 已提交
183

J
Joao Moreno 已提交
184
	constructor(public defaultDelay: number) {
E
Erich Gamma 已提交
185 186
		this.timeout = null;
		this.completionPromise = null;
187
		this.doResolve = null;
E
Erich Gamma 已提交
188 189 190
		this.task = null;
	}

J
Johannes Rieken 已提交
191
	trigger(task: ITask<T | Promise<T>>, delay: number = this.defaultDelay): Promise<T> {
E
Erich Gamma 已提交
192 193 194 195
		this.task = task;
		this.cancelTimeout();

		if (!this.completionPromise) {
J
Joao Moreno 已提交
196
			this.completionPromise = new Promise((c, e) => {
197 198
				this.doResolve = c;
				this.doReject = e;
E
Erich Gamma 已提交
199 200
			}).then(() => {
				this.completionPromise = null;
201
				this.doResolve = null;
A
Alex Dima 已提交
202
				const task = this.task!;
E
Erich Gamma 已提交
203 204
				this.task = null;

J
Joao Moreno 已提交
205
				return task();
E
Erich Gamma 已提交
206 207 208 209 210
			});
		}

		this.timeout = setTimeout(() => {
			this.timeout = null;
A
Alex Dima 已提交
211
			this.doResolve!(null);
E
Erich Gamma 已提交
212 213 214 215 216
		}, delay);

		return this.completionPromise;
	}

J
Joao Moreno 已提交
217
	isTriggered(): boolean {
E
Erich Gamma 已提交
218 219 220
		return this.timeout !== null;
	}

J
Joao Moreno 已提交
221
	cancel(): void {
E
Erich Gamma 已提交
222 223 224
		this.cancelTimeout();

		if (this.completionPromise) {
225
			this.doReject(errors.canceled());
E
Erich Gamma 已提交
226 227 228 229 230 231 232 233 234 235
			this.completionPromise = null;
		}
	}

	private cancelTimeout(): void {
		if (this.timeout !== null) {
			clearTimeout(this.timeout);
			this.timeout = null;
		}
	}
236 237 238 239

	dispose(): void {
		this.cancelTimeout();
	}
E
Erich Gamma 已提交
240 241 242 243 244 245
}

/**
 * A helper to delay execution of a task that is being requested often, while
 * preventing accumulation of consecutive executions, while the task runs.
 *
246 247 248 249
 * 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.
E
Erich Gamma 已提交
250
 */
J
Joao Moreno 已提交
251
export class ThrottledDelayer<T> {
E
Erich Gamma 已提交
252

J
Johannes Rieken 已提交
253
	private delayer: Delayer<Promise<T>>;
E
Erich Gamma 已提交
254 255 256
	private throttler: Throttler;

	constructor(defaultDelay: number) {
J
Joao Moreno 已提交
257
		this.delayer = new Delayer(defaultDelay);
E
Erich Gamma 已提交
258 259 260
		this.throttler = new Throttler();
	}

J
Johannes Rieken 已提交
261 262
	trigger(promiseFactory: ITask<Promise<T>>, delay?: number): Promise<T> {
		return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as any as Promise<T>;
J
Joao Moreno 已提交
263 264 265 266 267 268 269 270 271 272 273 274
	}

	isTriggered(): boolean {
		return this.delayer.isTriggered();
	}

	cancel(): void {
		this.delayer.cancel();
	}

	dispose(): void {
		this.delayer.dispose();
E
Erich Gamma 已提交
275 276 277
	}
}

J
Joao Moreno 已提交
278 279 280 281
/**
 * A barrier that is initially closed and then becomes opened permanently.
 */
export class Barrier {
E
Erich Gamma 已提交
282

J
Joao Moreno 已提交
283
	private _isOpen: boolean;
J
Johannes Rieken 已提交
284
	private _promise: Promise<boolean>;
J
Joao Moreno 已提交
285
	private _completePromise: (v: boolean) => void;
E
Erich Gamma 已提交
286 287

	constructor() {
J
Joao Moreno 已提交
288
		this._isOpen = false;
J
Johannes Rieken 已提交
289
		this._promise = new Promise<boolean>((c, e) => {
J
Joao Moreno 已提交
290
			this._completePromise = c;
E
Erich Gamma 已提交
291 292 293
		});
	}

J
Joao Moreno 已提交
294 295
	isOpen(): boolean {
		return this._isOpen;
E
Erich Gamma 已提交
296 297
	}

J
Joao Moreno 已提交
298 299 300
	open(): void {
		this._isOpen = true;
		this._completePromise(true);
E
Erich Gamma 已提交
301 302
	}

J
Johannes Rieken 已提交
303
	wait(): Promise<boolean> {
J
Joao Moreno 已提交
304
		return this._promise;
E
Erich Gamma 已提交
305 306 307
	}
}

308
export function timeout(millis: number): CancelablePromise<void>;
J
Johannes Rieken 已提交
309 310
export function timeout(millis: number, token: CancellationToken): Promise<void>;
export function timeout(millis: number, token?: CancellationToken): CancelablePromise<void> | Promise<void> {
311 312 313 314 315 316 317 318 319
	if (!token) {
		return createCancelablePromise(token => timeout(millis, token));
	}

	return new Promise((resolve, reject) => {
		const handle = setTimeout(resolve, millis);
		token.onCancellationRequested(() => {
			clearTimeout(handle);
			reject(errors.canceled());
320 321
		});
	});
322 323
}

324
export function disposableTimeout(handler: () => void, timeout = 0): IDisposable {
325
	const timer = setTimeout(handler, timeout);
J
Joao Moreno 已提交
326
	return toDisposable(() => clearTimeout(timer));
327 328
}

E
Erich Gamma 已提交
329 330 331 332 333
/**
 * Returns a new promise that joins the provided promise. Upon completion of
 * the provided promise the provided function will always be called. This
 * method is comparable to a try-finally code block.
 * @param promise a promise
J
Johannes Rieken 已提交
334
 * @param callback a function that will be call in the success and error case.
E
Erich Gamma 已提交
335
 */
J
Johannes Rieken 已提交
336
export function always<T>(promise: Promise<T>, callback: () => void): Promise<T> {
J
Johannes Rieken 已提交
337 338 339 340 341 342
	function safeCallback() {
		try {
			callback();
		} catch (err) {
			errors.onUnexpectedError(err);
		}
J
Johannes Rieken 已提交
343
	}
344 345
	promise.then(_ => safeCallback(), _ => safeCallback());
	return Promise.resolve(promise);
E
Erich Gamma 已提交
346 347
}

J
Johannes Rieken 已提交
348
export function ignoreErrors<T>(promise: Promise<T>): Promise<T | undefined> {
I
isidor 已提交
349 350 351
	return promise.then(undefined, _ => undefined);
}

E
Erich Gamma 已提交
352 353 354 355
/**
 * Runs the provided list of promise factories in sequential order. The returned
 * promise will complete to an array of results from each promise.
 */
356

J
Johannes Rieken 已提交
357
export function sequence<T>(promiseFactories: ITask<Promise<T>>[]): Promise<T[]> {
J
Johannes Rieken 已提交
358
	const results: T[] = [];
359 360
	let index = 0;
	const len = promiseFactories.length;
E
Erich Gamma 已提交
361

J
Johannes Rieken 已提交
362
	function next(): Promise<T> | null {
363
		return index < len ? promiseFactories[index++]() : null;
E
Erich Gamma 已提交
364 365
	}

J
Johannes Rieken 已提交
366
	function thenHandler(result: any): Promise<any> {
367
		if (result !== undefined && result !== null) {
E
Erich Gamma 已提交
368 369 370
			results.push(result);
		}

J
Johannes Rieken 已提交
371
		const n = next();
E
Erich Gamma 已提交
372 373 374 375
		if (n) {
			return n.then(thenHandler);
		}

376
		return Promise.resolve(results);
E
Erich Gamma 已提交
377 378
	}

379
	return Promise.resolve(null).then(thenHandler);
E
Erich Gamma 已提交
380 381
}

J
Johannes Rieken 已提交
382
export function first<T>(promiseFactories: ITask<Promise<T>>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T | null = null): Promise<T | null> {
383 384 385
	let index = 0;
	const len = promiseFactories.length;

A
Alex Dima 已提交
386
	const loop: () => Promise<T | null> = () => {
387 388 389
		if (index >= len) {
			return Promise.resolve(defaultValue);
		}
J
Joao Moreno 已提交
390

391
		const factory = promiseFactories[index++];
392
		const promise = Promise.resolve(factory());
J
Joao Moreno 已提交
393 394 395

		return promise.then(result => {
			if (shouldStop(result)) {
396
				return Promise.resolve(result);
J
Joao Moreno 已提交
397 398 399 400 401 402 403 404 405
			}

			return loop();
		});
	};

	return loop();
}

J
Joao Moreno 已提交
406
interface ILimitedTaskFactory<T> {
J
Johannes Rieken 已提交
407 408
	factory: ITask<Promise<T>>;
	c: (value?: T | Promise<T>) => void;
J
Joao Moreno 已提交
409
	e: (error?: any) => void;
E
Erich Gamma 已提交
410 411 412 413 414 415 416
}

/**
 * A helper to queue N promises and run them all with a max degree of parallelism. The helper
 * ensures that at any time no more than M promises are running at the same time.
 */
export class Limiter<T> {
J
Joao Moreno 已提交
417 418

	private _size = 0;
I
isidor 已提交
419
	private runningPromises: number;
E
Erich Gamma 已提交
420
	private maxDegreeOfParalellism: number;
J
Joao Moreno 已提交
421
	private outstandingPromises: ILimitedTaskFactory<T>[];
M
Matt Bierner 已提交
422
	private readonly _onFinished: Emitter<void>;
E
Erich Gamma 已提交
423 424 425 426

	constructor(maxDegreeOfParalellism: number) {
		this.maxDegreeOfParalellism = maxDegreeOfParalellism;
		this.outstandingPromises = [];
I
isidor 已提交
427
		this.runningPromises = 0;
428 429 430 431 432
		this._onFinished = new Emitter<void>();
	}

	public get onFinished(): Event<void> {
		return this._onFinished.event;
E
Erich Gamma 已提交
433 434
	}

435
	public get size(): number {
J
Joao Moreno 已提交
436 437
		return this._size;
		// return this.runningPromises + this.outstandingPromises.length;
438 439
	}

J
Johannes Rieken 已提交
440
	queue(factory: ITask<Promise<T>>): Promise<T> {
J
Joao Moreno 已提交
441
		this._size++;
E
Erich Gamma 已提交
442

J
Joao Moreno 已提交
443 444
		return new Promise<T>((c, e) => {
			this.outstandingPromises.push({ factory, c, e });
E
Erich Gamma 已提交
445 446 447 448 449
			this.consume();
		});
	}

	private consume(): void {
I
isidor 已提交
450
		while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) {
A
Alex Dima 已提交
451
			const iLimitedTask = this.outstandingPromises.shift()!;
I
isidor 已提交
452
			this.runningPromises++;
E
Erich Gamma 已提交
453

J
Johannes Rieken 已提交
454
			const promise = iLimitedTask.factory();
J
Johannes Rieken 已提交
455 456
			promise.then(iLimitedTask.c, iLimitedTask.e);
			promise.then(() => this.consumed(), () => this.consumed());
E
Erich Gamma 已提交
457 458 459
		}
	}

I
isidor 已提交
460
	private consumed(): void {
J
Joao Moreno 已提交
461
		this._size--;
I
isidor 已提交
462
		this.runningPromises--;
463 464 465 466 467 468 469 470 471 472

		if (this.outstandingPromises.length > 0) {
			this.consume();
		} else {
			this._onFinished.fire();
		}
	}

	public dispose(): void {
		this._onFinished.dispose();
E
Erich Gamma 已提交
473 474 475
	}
}

B
Benjamin Pasero 已提交
476 477 478 479 480 481 482 483 484 485
/**
 * A queue is handles one promise at a time and guarantees that at any time only one promise is executing.
 */
export class Queue<T> extends Limiter<T> {

	constructor() {
		super(1);
	}
}

486 487 488 489
/**
 * A helper to organize queues per resource. The ResourceQueue makes sure to manage queues per resource
 * by disposing them once the queue is empty.
 */
B
Benjamin Pasero 已提交
490
export class ResourceQueue {
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
	private queues: { [path: string]: Queue<void> };

	constructor() {
		this.queues = Object.create(null);
	}

	public queueFor(resource: URI): Queue<void> {
		const key = resource.toString();
		if (!this.queues[key]) {
			const queue = new Queue<void>();
			queue.onFinished(() => {
				queue.dispose();
				delete this.queues[key];
			});

			this.queues[key] = queue;
		}

		return this.queues[key];
	}
}

A
Alex Dima 已提交
513
export class TimeoutTimer extends Disposable {
514
	private _token: any;
A
Alex Dima 已提交
515

516 517 518
	constructor();
	constructor(runner: () => void, timeout: number);
	constructor(runner?: () => void, timeout?: number) {
A
Alex Dima 已提交
519 520
		super();
		this._token = -1;
521 522 523 524

		if (typeof runner === 'function' && typeof timeout === 'number') {
			this.setIfNotSet(runner, timeout);
		}
A
Alex Dima 已提交
525 526
	}

J
Joao Moreno 已提交
527
	dispose(): void {
A
Alex Dima 已提交
528 529 530 531
		this.cancel();
		super.dispose();
	}

J
Joao Moreno 已提交
532
	cancel(): void {
A
Alex Dima 已提交
533
		if (this._token !== -1) {
A
Alex Dima 已提交
534
			clearTimeout(this._token);
A
Alex Dima 已提交
535 536 537 538
			this._token = -1;
		}
	}

J
Johannes Rieken 已提交
539
	cancelAndSet(runner: () => void, timeout: number): void {
A
Alex Dima 已提交
540
		this.cancel();
A
Alex Dima 已提交
541
		this._token = setTimeout(() => {
A
Alex Dima 已提交
542 543 544 545 546
			this._token = -1;
			runner();
		}, timeout);
	}

J
Joao Moreno 已提交
547
	setIfNotSet(runner: () => void, timeout: number): void {
A
Alex Dima 已提交
548 549 550 551
		if (this._token !== -1) {
			// timer is already set
			return;
		}
A
Alex Dima 已提交
552
		this._token = setTimeout(() => {
A
Alex Dima 已提交
553 554 555 556 557 558 559
			this._token = -1;
			runner();
		}, timeout);
	}
}

export class IntervalTimer extends Disposable {
J
Joao Moreno 已提交
560

561
	private _token: any;
A
Alex Dima 已提交
562 563 564 565 566 567

	constructor() {
		super();
		this._token = -1;
	}

J
Joao Moreno 已提交
568
	dispose(): void {
A
Alex Dima 已提交
569 570 571 572
		this.cancel();
		super.dispose();
	}

J
Joao Moreno 已提交
573
	cancel(): void {
A
Alex Dima 已提交
574
		if (this._token !== -1) {
A
Alex Dima 已提交
575
			clearInterval(this._token);
A
Alex Dima 已提交
576 577 578 579
			this._token = -1;
		}
	}

J
Johannes Rieken 已提交
580
	cancelAndSet(runner: () => void, interval: number): void {
A
Alex Dima 已提交
581
		this.cancel();
A
Alex Dima 已提交
582
		this._token = setInterval(() => {
A
Alex Dima 已提交
583 584 585 586 587
			runner();
		}, interval);
	}
}

E
Erich Gamma 已提交
588 589
export class RunOnceScheduler {

A
Alex Dima 已提交
590
	protected runner: ((...args: any[]) => void) | null;
591

592
	private timeoutToken: any;
E
Erich Gamma 已提交
593 594 595
	private timeout: number;
	private timeoutHandler: () => void;

596
	constructor(runner: (...args: any[]) => void, timeout: number) {
E
Erich Gamma 已提交
597 598 599 600 601 602 603 604 605
		this.timeoutToken = -1;
		this.runner = runner;
		this.timeout = timeout;
		this.timeoutHandler = this.onTimeout.bind(this);
	}

	/**
	 * Dispose RunOnceScheduler
	 */
J
Joao Moreno 已提交
606
	dispose(): void {
E
Erich Gamma 已提交
607 608 609 610 611
		this.cancel();
		this.runner = null;
	}

	/**
612
	 * Cancel current scheduled runner (if any).
E
Erich Gamma 已提交
613
	 */
J
Joao Moreno 已提交
614
	cancel(): void {
I
isidor 已提交
615
		if (this.isScheduled()) {
A
Alex Dima 已提交
616
			clearTimeout(this.timeoutToken);
E
Erich Gamma 已提交
617 618 619 620 621 622 623
			this.timeoutToken = -1;
		}
	}

	/**
	 * Cancel previous runner (if any) & schedule a new runner.
	 */
J
Joao Moreno 已提交
624
	schedule(delay = this.timeout): void {
E
Erich Gamma 已提交
625
		this.cancel();
A
Alex Dima 已提交
626
		this.timeoutToken = setTimeout(this.timeoutHandler, delay);
E
Erich Gamma 已提交
627 628
	}

I
isidor 已提交
629 630 631
	/**
	 * Returns true if scheduled.
	 */
J
Joao Moreno 已提交
632
	isScheduled(): boolean {
I
isidor 已提交
633 634 635
		return this.timeoutToken !== -1;
	}

E
Erich Gamma 已提交
636 637 638
	private onTimeout() {
		this.timeoutToken = -1;
		if (this.runner) {
639
			this.doRun();
E
Erich Gamma 已提交
640 641
		}
	}
642 643

	protected doRun(): void {
A
Alex Dima 已提交
644 645 646
		if (this.runner) {
			this.runner();
		}
647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
	}
}

export class RunOnceWorker<T> extends RunOnceScheduler {
	private units: T[] = [];

	constructor(runner: (units: T[]) => void, timeout: number) {
		super(runner, timeout);
	}

	work(unit: T): void {
		this.units.push(unit);

		if (!this.isScheduled()) {
			this.schedule();
		}
	}

	protected doRun(): void {
		const units = this.units;
		this.units = [];

A
Alex Dima 已提交
669 670 671
		if (this.runner) {
			this.runner(units);
		}
672 673 674 675 676 677 678
	}

	dispose(): void {
		this.units = [];

		super.dispose();
	}
E
Erich Gamma 已提交
679 680
}

S
Sandeep Somavarapu 已提交
681 682
export function nfcall(fn: Function, ...args: any[]): Promise<any>;
export function nfcall<T>(fn: Function, ...args: any[]): Promise<T>;
E
Erich Gamma 已提交
683
export function nfcall(fn: Function, ...args: any[]): any {
J
Joao Moreno 已提交
684
	return new Promise((c, e) => fn(...args, (err: any, result: any) => err ? e(err) : c(result)));
E
Erich Gamma 已提交
685 686
}

J
Johannes Rieken 已提交
687 688
export function ninvoke(thisArg: any, fn: Function, ...args: any[]): Promise<any>;
export function ninvoke<T>(thisArg: any, fn: Function, ...args: any[]): Promise<T>;
E
Erich Gamma 已提交
689
export function ninvoke(thisArg: any, fn: Function, ...args: any[]): any {
J
Johannes Rieken 已提交
690
	return new Promise((resolve, reject) => fn.call(thisArg, ...args, (err: any, result: any) => err ? reject(err) : resolve(result)));
691
}
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713


//#region -- run on idle tricks ------------

export interface IdleDeadline {
	readonly didTimeout: boolean;
	timeRemaining(): DOMHighResTimeStamp;
}
/**
 * Execute the callback the next time the browser is idle
 */
export let runWhenIdle: (callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable;

declare function requestIdleCallback(callback: (args: IdleDeadline) => void, options?: { timeout: number }): number;
declare function cancelIdleCallback(handle: number): void;

(function () {
	if (typeof requestIdleCallback !== 'function' || typeof cancelIdleCallback !== 'function') {
		let dummyIdle: IdleDeadline = Object.freeze({
			didTimeout: true,
			timeRemaining() { return 15; }
		});
714 715
		runWhenIdle = (runner) => {
			let handle = setTimeout(() => runner(dummyIdle));
A
Alex Dima 已提交
716 717 718 719 720 721 722 723 724 725
			let disposed = false;
			return {
				dispose() {
					if (disposed) {
						return;
					}
					disposed = true;
					clearTimeout(handle);
				}
			};
726 727 728
		};
	} else {
		runWhenIdle = (runner, timeout?) => {
A
Alex Dima 已提交
729 730 731 732 733 734 735 736 737 738 739
			let handle: number = requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined);
			let disposed = false;
			return {
				dispose() {
					if (disposed) {
						return;
					}
					disposed = true;
					cancelIdleCallback(handle);
				}
			};
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786
		};
	}
})();

/**
 * An implementation of the "idle-until-urgent"-strategy as introduced
 * here: https://philipwalton.com/articles/idle-until-urgent/
 */
export class IdleValue<T> {

	private readonly _executor: () => void;
	private readonly _handle: IDisposable;

	private _didRun: boolean;
	private _value: T;
	private _error: any;

	constructor(executor: () => T) {
		this._executor = () => {
			try {
				this._value = executor();
			} catch (err) {
				this._error = err;
			} finally {
				this._didRun = true;
			}
		};
		this._handle = runWhenIdle(() => this._executor());
	}

	dispose(): void {
		this._handle.dispose();
	}

	getValue(): T {
		if (!this._didRun) {
			this._handle.dispose();
			this._executor();
		}
		if (this._error) {
			throw this._error;
		}
		return this._value;
	}
}

//#endregion