azure-devops-ui
Version:
React components for building web UI in Azure DevOps
134 lines (133 loc) • 4.46 kB
JavaScript
import * as React from "react";
/**
* makeCancelable is used to wrap an existing promise and support canceling
* the promise. This doesnt actually stop the promise from completing, instead
* it will send an isCanceled value to the resolve and reject methods when
* the promise is canceled.
*/
export const makeCancelable = (promise) => {
let isCanceled = false;
const wrappedPromise = new Promise((resolve, reject) => {
const rejectIfCanceled = () => {
if (isCanceled) {
// Add an empty reject handler to avoid browser console errors
// every time a promise is canceled.
wrappedPromise.catch(() => { });
reject({ isCanceled: true });
}
return isCanceled;
};
promise.then(val => rejectIfCanceled() || resolve(val));
promise.catch(error => rejectIfCanceled() || reject(error));
});
return {
promise: wrappedPromise,
cancel() {
isCanceled = true;
}
};
};
/**
* Returns a promise that, if the given promise resolves in less than timeoutMs, resolves to the
* resolution (or rejection) of the given promise. If the given promise does not resolve in less
* than timeoutMs, reject with the given message.
* @param promise
* @param timeoutMs
* @param message message to send with the rejection when the timeout expires
*/
export function timeout(promise, timeoutMs, message) {
return new Promise((resolve, reject) => {
const timeoutHandle = setTimeout(() => {
reject(message == null ? `Timed out after ${timeoutMs} ms.` : message);
}, timeoutMs);
// Maybe use finally when it's available.
promise.then(result => {
resolve(result);
clearTimeout(timeoutHandle);
}, reason => {
reject(reason);
clearTimeout(timeoutHandle);
});
});
}
/**
* Returns a promise that resolves after timeoutMs.
* @param timeoutMs
*/
export async function wait(timeoutMs) {
return new Promise(resolve => {
setTimeout(resolve, timeoutMs);
});
}
/**
* Return a promise that resolves at least delayMs from now. Rejection happens immediately.
*/
export async function delay(promise, delayMs) {
return (await Promise.all([promise, wait(delayMs)]))[0];
}
/**
* Returns a promise that resolves after all given promises have either resolved or rejected.
* @deprecated just use Promise.allSettled() now.
*/
export function allSettled(promises) {
const results = new Array(promises.length);
return new Promise(resolve => {
let count = 0;
for (let i = 0; i < promises.length; ++i) {
const promise = promises[i];
promise
.then(result => {
results[i] = {
state: "fulfilled",
value: result
};
}, reason => {
results[i] = {
state: "rejected",
reason: reason
};
})
.then(() => {
if (++count === promises.length) {
resolve(results);
}
});
}
});
}
/**
* Custom hook to cancel promises when the component unmounts
*
* Example:
* ```
* const { trackPromise } = usePromise();
*
* useEffect(() => {
* const x: Promise<number> = svc.doSomethingAsync();
* trackPromise(x).then(() => ...);
* });
* ```
*/
export function usePromise() {
const promises = React.useRef([]);
React.useEffect(() => {
// Called when the component unmounts to cancel all pending promises
return () => {
if (promises.current) {
promises.current.forEach(p => p.cancel());
promises.current = null;
}
};
}, []);
const trackPromise = React.useCallback((originalPromise) => {
const cancelablePromise = makeCancelable(originalPromise);
if (!promises.current) {
throw new Error("usePromise hook: `trackPromise` called after using component was unmounted.");
}
promises.current.push(cancelablePromise);
return cancelablePromise.promise;
}, []);
return {
trackPromise
};
}