UNPKG

prex-es5

Version:

Async coordination primitives and extensions on top of ES6 Promises

180 lines (177 loc) 6.92 kB
/*! ***************************************************************************** Copyright (c) Microsoft Corporation. Licensed under the Apache License, Version 2.0. See LICENSE file in the project root for details. ***************************************************************************** */ import { LinkedList } from "./list"; import { CancellationToken, CancelError } from "./cancellation"; import { isMissing, isFunction, isNumber, isInstance } from "./utils"; /** * Enables multiple tasks to cooperatively work on an algorithm through * multiple phases. */ export class Barrier { /** * Initializes a new instance of the Barrier class. * * @param participantCount The initial number of participants for the barrier. * @param postPhaseAction An action to execute between each phase. */ constructor(participantCount, postPhaseAction) { this._isExecutingPostPhaseAction = false; this._phaseNumber = 0; this._waiters = new LinkedList(); if (!isNumber(participantCount)) throw new TypeError("Number expected: participantCount."); if ((participantCount |= 0) < 0) throw new RangeError("Argument out of range: participantCount."); if (!isFunction(postPhaseAction, /*optional*/ true)) throw new TypeError("Function expected: postPhaseAction."); this._participantCount = participantCount; this._remainingParticipants = participantCount; this._postPhaseAction = postPhaseAction; } /** * Gets the number of the Barrier's current phase. */ get currentPhaseNumber() { return this._phaseNumber; } /** * Gets the total number of participants in the barrier. */ get participantCount() { return this._participantCount; } /** * Gets the number of participants in the barrier that haven't yet signaled in the current phase. */ get remainingParticipants() { return this._remainingParticipants; } /** * Notifies the Barrier there will be additional participants. * * @param participantCount The number of additional participants. */ add(participantCount) { if (isMissing(participantCount)) participantCount = 1; if (!isNumber(participantCount)) throw new TypeError("Number expected: participantCount."); if ((participantCount |= 0) <= 0) throw new RangeError("Argument out of range: participantCount."); if (this._isExecutingPostPhaseAction) throw new Error("This method may not be called from within the postPhaseAction."); this._participantCount += participantCount; this._remainingParticipants += participantCount; } /** * Notifies the Barrier there will be fewer participants. * * @param participantCount The number of participants to remove. */ remove(participantCount) { if (isMissing(participantCount)) participantCount = 1; if (!isNumber(participantCount)) throw new TypeError("Number expected: participantCount."); if ((participantCount |= 0) <= 0) throw new RangeError("Argument out of range: participantCount."); if (this._participantCount < participantCount) throw new RangeError("Argument out of range: participantCount."); if (this._isExecutingPostPhaseAction) throw new Error("This method may not be called from within the postPhaseAction."); this._participantCount -= participantCount; this._remainingParticipants -= participantCount; if (this._participantCount === 0) { this._finishPhase(); } } /** * Signals that a participant has reached the barrier and waits for all other participants * to reach the barrier. * * @param token An optional CancellationToken used to cancel the request. */ signalAndWait(token) { return new Promise((resolve, reject) => { if (isMissing(token)) token = CancellationToken.none; if (!isInstance(token, CancellationToken)) throw new TypeError("CancellationToken expected: token."); token.throwIfCancellationRequested(); if (this._isExecutingPostPhaseAction) throw new Error("This method may not be called from within the postPhaseAction."); if (this._participantCount === 0) throw new Error("The barrier has no registered participants."); if (this._remainingParticipants === 0) throw new Error("The number of operations using the barrier exceeded the number of registered participants."); const node = this._waiters.push({ resolve: () => { registration.unregister(); if (token.cancellationRequested) { reject(new CancelError()); } else { resolve(); } }, reject: reason => { registration.unregister(); if (token.cancellationRequested) { reject(new CancelError()); } else { reject(reason); } } }); const registration = token.register(() => { if (node.list) { node.list.deleteNode(node); reject(new CancelError()); } }); this._remainingParticipants--; if (this._remainingParticipants === 0) { this._finishPhase(); } }); } _finishPhase() { const postPhaseAction = this._postPhaseAction; if (postPhaseAction) { this._isExecutingPostPhaseAction = true; Promise .resolve() .then(() => postPhaseAction(this)) .then(() => this._resolveNextPhase(), error => this._rejectNextPhase(error)); } else { Promise .resolve() .then(() => this._resolveNextPhase()); } } _nextPhase() { this._isExecutingPostPhaseAction = false; this._remainingParticipants = this._participantCount; this._phaseNumber++; } _resolveNextPhase() { this._nextPhase(); for (const deferred of this._waiters.drain()) { if (deferred) deferred.resolve(); } } _rejectNextPhase(error) { this._nextPhase(); for (const deferred of this._waiters.drain()) { if (deferred) deferred.reject(error); } } } //# sourceMappingURL=barrier.js.map