prex-es5
Version:
Async coordination primitives and extensions on top of ES6 Promises
180 lines (177 loc) • 6.92 kB
JavaScript
/*! *****************************************************************************
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