UNPKG

prex

Version:

Async coordination primitives and extensions on top of ES6 Promises

179 lines (177 loc) 7.09 kB
"use strict"; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Licensed under the Apache License, Version 2.0. See LICENSE file in the project root for details. ***************************************************************************** */ Object.defineProperty(exports, "__esModule", { value: true }); const list_1 = require("./list"); const cancellation_1 = require("./cancellation"); const utils_1 = require("./utils"); const adapter_1 = require("./adapter"); /** * Enables multiple tasks to cooperatively work on an algorithm through * multiple phases. */ 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 list_1.LinkedList(); if (!utils_1.isNumber(participantCount)) throw new TypeError("Number expected: participantCount."); if ((participantCount |= 0) < 0) throw new RangeError("Argument out of range: participantCount."); if (!utils_1.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 (utils_1.isMissing(participantCount)) participantCount = 1; if (!utils_1.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 (utils_1.isMissing(participantCount)) participantCount = 1; if (!utils_1.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) => { const _token = adapter_1.getToken(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 cancellation_1.CancelError()); } else { resolve(); } }, reject: reason => { registration.unregister(); if (_token.cancellationRequested) { reject(new cancellation_1.CancelError()); } else { reject(reason); } } }); const registration = _token.register(() => { if (node.list) { node.list.deleteNode(node); reject(new cancellation_1.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); } } } exports.Barrier = Barrier;