UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

134 lines (133 loc) 4.46 kB
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 }; }