@whatwg-node/promise-helpers
Version:
Promise helpers
198 lines (197 loc) • 5.83 kB
JavaScript
export function isPromise(value) {
return value?.then != null;
}
export function handleMaybePromise(inputFactory, outputSuccessFactory, outputErrorFactory) {
function _handleMaybePromise() {
const input$ = inputFactory();
if (isPromise(input$)) {
return input$.then(outputSuccessFactory, outputErrorFactory);
}
return outputSuccessFactory(input$);
}
if (!outputErrorFactory) {
return _handleMaybePromise();
}
try {
return _handleMaybePromise();
}
catch (err) {
return outputErrorFactory(err);
}
}
export function fakePromise(value) {
if (isPromise(value)) {
return value;
}
// Write a fake promise to avoid the promise constructor
// being called with `new Promise` in the browser.
return {
then(resolve) {
if (resolve) {
const callbackResult = resolve(value);
if (isPromise(callbackResult)) {
return callbackResult;
}
return fakePromise(callbackResult);
}
return this;
},
catch() {
return this;
},
finally(cb) {
if (cb) {
const callbackResult = cb();
if (isPromise(callbackResult)) {
return callbackResult.then(() => value, () => value);
}
return fakePromise(value);
}
return this;
},
[Symbol.toStringTag]: 'Promise',
};
}
export function createDeferredPromise() {
if (Promise.withResolvers) {
return Promise.withResolvers();
}
let resolveFn;
let rejectFn;
const promise = new Promise(function deferredPromiseExecutor(resolve, reject) {
resolveFn = resolve;
rejectFn = reject;
});
return {
promise,
get resolve() {
return resolveFn;
},
get reject() {
return rejectFn;
},
};
}
export { iterateAsync as iterateAsyncVoid };
export function iterateAsync(iterable, callback, results) {
if (iterable?.length === 0) {
return;
}
const iterator = iterable[Symbol.iterator]();
function iterate() {
const { done: endOfIterator, value } = iterator.next();
if (endOfIterator) {
return;
}
let endedEarly = false;
function endEarly() {
endedEarly = true;
}
return handleMaybePromise(() => callback(value, endEarly), result => {
if (endedEarly) {
return;
}
if (result) {
results?.push(result);
}
return iterate();
});
}
return iterate();
}
export function fakeRejectPromise(error) {
if (isPromise(error)) {
return error;
}
return {
then() {
return this;
},
catch(reject) {
if (reject) {
return fakePromise(reject(error));
}
return this;
},
finally(cb) {
if (cb) {
cb();
}
return this;
},
[Symbol.toStringTag]: 'Promise',
};
}
export function mapMaybePromise(input, onSuccess, onError) {
return handleMaybePromise(() => input, onSuccess, onError);
}
/**
* Given an AsyncIterable and a callback function, return an AsyncIterator
* which produces values mapped via calling the callback function.
*/
export function mapAsyncIterator(iterator, onNext, onError, onEnd) {
if (Symbol.asyncIterator in iterator) {
iterator = iterator[Symbol.asyncIterator]();
}
let $return;
let abruptClose;
let onEndWithValue;
if (onEnd) {
let onEndWithValueResult /** R in onEndWithValue */;
onEndWithValue = value => {
onEndWithValueResult ||= handleMaybePromise(onEnd, () => value, () => value);
return onEndWithValueResult;
};
}
if (typeof iterator.return === 'function') {
$return = iterator.return;
abruptClose = (error) => {
const rethrow = () => {
throw error;
};
return $return.call(iterator).then(rethrow, rethrow);
};
}
function mapResult(result) {
if (result.done) {
return onEndWithValue ? onEndWithValue(result) : result;
}
return handleMaybePromise(() => result.value, value => handleMaybePromise(() => onNext(value), iteratorResult, abruptClose));
}
let mapReject;
if (onError) {
let onErrorResult;
// Capture rejectCallback to ensure it cannot be null.
const reject = onError;
mapReject = (error) => {
onErrorResult ||= handleMaybePromise(() => error, error => handleMaybePromise(() => reject(error), iteratorResult, abruptClose));
return onErrorResult;
};
}
return {
next() {
return iterator.next().then(mapResult, mapReject);
},
return() {
const res$ = $return
? $return.call(iterator).then(mapResult, mapReject)
: fakePromise({ value: undefined, done: true });
return onEndWithValue ? res$.then(onEndWithValue) : res$;
},
throw(error) {
if (typeof iterator.throw === 'function') {
return iterator.throw(error).then(mapResult, mapReject);
}
if (abruptClose) {
return abruptClose(error);
}
return fakeRejectPromise(error);
},
[Symbol.asyncIterator]() {
return this;
},
};
}
function iteratorResult(value) {
return { value, done: false };
}