UNPKG

suspenders-js

Version:

Asynchronous programming library utilizing coroutines, functional reactive programming and structured concurrency.

121 lines 4.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Channel = void 0; /** * Channels are used to send and receive messages between coroutines. Channels can be buffered so * that senders do not suspend if there isn't a receiver waiting to receive the next message. * If there are multiple coroutines receiving on the same channel, they receive new values in first * come first served order. */ class Channel { constructor(options) { this._receiverSuspenders = []; this._buffer = []; /** * Receives a message on this channel. A buffered message is received or if there is none, this * suspends the receiver until one is available. Multiple suspended receivers are resumed in first * come first served order. * @param resultCallback */ this.receive = (resultCallback) => { const valueCallback = this._buffer.shift(); // check for a queued value if (valueCallback !== undefined) { const senderSuccessCallback = valueCallback[1]; // resume sender first to get round robin order // resume sender if suspended if (senderSuccessCallback !== undefined) { senderSuccessCallback({ value: undefined }); } // resume receiver resultCallback({ value: valueCallback[0] }); // count how many suspended senders can be resumed to fill buffer to max let resumeCount = 0; let index = 0; for (let index = 0; index < this._buffer.length; index++) { if (resumeCount >= this._bufferSize) { break; } const pair = this._buffer[index]; const senderSuccessCallback = pair[1]; // resumes sender by buffering it's value if (senderSuccessCallback !== undefined) { senderSuccessCallback({ value: undefined }); pair[1] = undefined; } resumeCount++; } return; } else { this._receiverSuspenders.push(resultCallback); return () => { const index = this._receiverSuspenders.findIndex((x) => x === resultCallback); if (index !== -1) { this._receiverSuspenders.splice(index, 1); } }; } }; this._bufferSize = options?.bufferSize ?? 0; } /** * Sends a message on this channel. If there is a queued receiver coroutine, it is resumed * immediately to process the message. If there are no queued receivers and buffer is full, the * sending coroutine is suspened until next message is received on this channel. Multiple * suspended senders are resumed in order they were suspended. * @param value */ send(value) { return (resultCallback) => { const receiver = this._receiverSuspenders.shift(); if (receiver !== undefined) { // receiver was waiting for value receiver({ value }); resultCallback({ value: undefined }); return; } else if (this._buffer.length < this._bufferSize) { // buffer value but don't block sender const valueCallback = [value, undefined]; this._buffer.push(valueCallback); resultCallback({ value: undefined }); return; } else { // block sender until receiver gets value const valueCallback = [value, resultCallback]; this._buffer.push(valueCallback); return () => { const index = this._buffer.findIndex((x) => x === valueCallback); if (index !== -1) { this._buffer.splice(index, 1); } }; } }; } /** * Tries to send a message on the channel. Returns true if successful, or false if buffer is full. * @param value */ trySend(value) { const receiver = this._receiverSuspenders.shift(); if (receiver !== undefined) { // receiver was waiting for value receiver({ value }); return true; } else if (this._buffer.length < this._bufferSize) { // buffer value const valueCallback = [value, undefined]; this._buffer.push(valueCallback); return true; } else { return false; } } } exports.Channel = Channel; //# sourceMappingURL=Channel.js.map