Module: util/promise

Methods

(async, static) wait(delayopt, passThroughArgopt) → {Promise.<T>}

Parameters:
Name Type Attributes Default Description
delay number <optional>
0

Delay in milliseconds before the returned promise resolves. If this parameter is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, as soon as possible. Note that in either case, the actual delay may be longer than intended (it uses setTimeout which does not make any guarantees).

passThroughArg T <optional>

Pass-through argument to resolve with, can be used to insert "wait()" in a chain to pass through a value to the next promise function.

This function returns a promise that resolves after delay milliseconds. It resolves with the passThroughArg, if one is supplied, or undefined if none is supplied. Example use case: In an IMAP message retrieval application we use this function to create a delay between retries after disconnects from the IMAP server to avoid triggering an alert on the server for too many and too frequent connections.

Source:
Returns:

Type: Promise.<T>

(async, static) timeout(delayopt, promise, txtopt) → {Promise.<T>}

Parameters:
Name Type Attributes Default Description
delay number <optional>
Infinity

Timeout in milliseconds. If the promise from the function has not resolved by then we return a rejected promise. If this parameter is omitted the promise is returned as-is because we assume "Infinity" as default delay value so that it is useless to even start the timer.
Note #1: A timeout of 0 is not allowed and will result in an error because that value is unpredictable. Does it mean to fail right away? If it really does is not certain because it depends on which task is in front of the JS runtime internal microtask- or in the event loop queue. We don't know where the promise we received will end up, it may already be resolved, or it may be scheduled later in the event queue.
Note #2: The actual delay may be longer than intended (it uses setTimeout which does not make any guarantees).

promise Promise.<T>

The promise that the timeout (if one is given) is applied to.

txt string <optional>
'[${delay} ms]'

Text to include in the Error message after a timeout

This function delivers a rejection if a given promise does not resolve or reject within the given time. The advantage of using this function over using a Promise.race() of ones promise against the promise-helper wait() function above is that it cancels the timer if the promise resolves before the timer fires. While a lingering timer has no influence on the value of the promise a program won't end as long as there are timers still running - so if you have a long-running timeout your app will be running for at least that long, even if it is actually finished a split second later. Canceling the timer prevents that problem with negligible effort and no other side-effects.

IMPORTANT: A timeout does not cancel the function that created the promise! It merely returns the rejection from the timeout instead of waiting for the result of the original promise.

Source:
Returns:

Returns the promise resulting from Promise.race-ing the received promise against (or with) a timeout promise. Whichever of the two is fulfilled first wins. If the timeout promise wins the returned promise is rejected with an Error whose name is "TimeoutError".

Type: Promise.<T>

(async, static) serializeWithType(type, …functions) → {Promise.<T>}

Parameters:
Name Type Attributes Description
type string

The given promise-producing functions are serialized together with any other ones we may already have serialized previously under the given type string.

functions function <repeatable>

List of arguments of type (...args) => Promise<T> — Functions that return promises or values, in the latter case the values are wrapped in a promise to be serializable. That means that synchronous functions will become asynchronous.

Make sure the given functions are executed sequentially. The "type" argument means we can add functions later, even much later and from a completely different location in the code. The purpose may be to coordinate access to a shared external resource without having to know which part of the code uses it. We remember the chain we build for that given type string. If there is more than one function in the argument list we serialize them in the specified order. Note: If any promise in the chain is rejected (directly or by throwing an error) subsequent functions are not impacted and will still be run! The serializer's function merely is to ensure that none of the given functions are ever being executed at the same time.

Source:
Returns:

Returns the last promise added to the chain. Anything attached via .then(..) to the returned promise is guaranteed to execute only after the chain is completed.

Type: Promise.<T>

(async, static) allSettled(promises) → {Promise.<AllSettled.<T>>}

Parameters:
Name Type Description
promises Array.<Promise.<T>>

An array of Promise objects

Implementation of Promise.allSettled. This function never rejects and all promises fulfill, the fulfilled/rejected status is instead supplied in a meta-object of the always-fulfilled promise. NOTE: Unlike the proposed function our implementation guarantees that rejection values are instances of Error. By invoking our centralized createError() when the value is not an Error we have a special error (with unique code) to get notified of such issues - rejections should really always be in the form of errors, for consistency and also for the stack trace.

Source:
Returns:

Type: Promise.<AllSettled.<T>>

(async, static) retry(fn, optionsopt) → {Promise.<T>}

Parameters:
Name Type Attributes Description
fn function

A function of type (...args) => Promise<T>

options object <optional>
Properties
Name Type Attributes Default Description
delay number <optional>
600

Delay between retries in milliseconds

retries number <optional>
3

How many times to retry

shouldRetry function <optional>

This optional function can return false after checking the Error object to prevent additional retries

delayMultiplier number <optional>
1

The delay is multiplied by this number at each attempt

The function returning a promise is executed again when the promise rejects until it either resolves or the maximum number of retries is reached.

Source:
Returns:

Resolves with whatever the given function fn returns

Type: Promise.<T>

(static) createTrackingPromise() → {TrackingPromiseObj.<T>}

This function creates a "passive tracking promise".

Usage

This function returns the promise as well as its resolve/reject callbacks, normally hidden inside the promise and used by code normally run through the promise, which a tracking promise does not have.

You keep the promise and give the resolve/reject callbacks to the function that you want to track, where previously you would have gotten an event emitter to subscribe to or given a callback function to. That code, instead of emitting an event or calling your callback function, now calls the resolve or the reject callback of the promise.

Explanation

Normally promises control all the code that produces their success or failure result. This tracking promise however does not.

Tracking promises are useful when a "3rd party" otherwise not involved with direct control wants to know the final outcome of an asynchronous procedure. For example, if we have a stream the code controlling the stream will not use a promise. There may however be a third party that only wants to know when the stream ends but does not care what happens during the stream.

While we could expose the details of the stream, the "error" event and the "finish" (or "end") events, for example, we find it much more convenient and also a nice abstraction across more than just streams to use this tracking promise device.

In addition, unlike event handlers the promise keeps the state once it has been set, so subscribing after the state-changing event already happened still gives the correct result. If we rely on event handlers instead we would have to have an additional public property or method to access the current state.

Errors reported through the tracking promise also may not be the actual errors but a generic one, after all, code using the tracking promise is not involved in any details.

The procedure to create a tracking promise is bloody: The visceral functions usually hidden deep inside the promise's peritoneum are forcefully exposed to the outside, one might say the whole promise is turned inside out. We know this is not "standard" but after giving it a lot of thought we still find the concept appealing for the given use case.

Default rejection handler

Tracking promises receive a default rejection catch() handler that discards and ignores any errors.

This has no influence if any handler, for rejection or for success, is attached to the promise at all.

It is used only in case the tracking promise is not used, if nothing is attached to it. This can happen, for example, if a tracking promise is an optional property in an API-object to indicate overall end with failure or success of a process. We use it for file streams on the system level, for example.

If the promise is used this default handler has no function and does not influence the behavior of the promise. That means any rejection handler will still catch the rejection, and any success handler creates a new promise that might reject if the underlying promise rejects and therefore always needs to be coupled with a rejection handler as usual, see https://stackoverflow.com/questions/42460039/promise-reject-causes-uncaught-in-promise-warning for an explanation.

Reasons for a tracking promise:

  • Hide internals: Is it an event based process like a stream, or something completely custom? It does not matter, if it fits the pattern "some async. process we are not involved in but would like to know when it ends - transmitting a result or failure is an additional feature.

  • Code that already uses promises or async/await to coordinate asynchronous activities can seamlessly use the promise instead of having extra (non-promise) code mixed with the promise-based one.

  • Tracking promises are used in place of events (error, end) or callbacks. When the asynchronous process uses those, but the code that wants either or both of 1) synchronization and 2) the result (success value, error) without controlling the process (in which case there would be no choice) may prefer a promise.

When not to use a tracking promise:

  • The code that wants to use a tracking promise is actually directly responsible for the asynchronous process. In that case it should use the actual constructs (e.g. events).

  • When it doesn't feel right :-)

Source:
Returns:

Returns an object with the tracking promise and its resolve and reject methods

Type: TrackingPromiseObj.<T>

Type Definitions

PromiseResolveCb(valueopt) → {undefined}

Parameters:
Name Type Attributes Description
value Promise.<T> | T <optional>

The value the promise will be resolved with

Type describing the resolve() callback function received by the new Promise() creation callback function. Copied from TypeScript's lib.es2015.promise.d.ts

Source:
Returns:

Returns undefined

Type: undefined

PromiseRejectCb(reason) → {undefined}

Parameters:
Name Type Description
reason Error

The error the promise is rejected with

Type describing the reject() callback function received by the new Promise() creation callback function. Copied from TypeScript's lib.es2015.promise.d.ts

Source:
Returns:

Returns undefined

Type: undefined