const is_promise = obj => !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'; const fire_and_forget = (a, on_error) => { (async () => { try { await a(); } catch(e) { if(on_error) on_error(e); else throw(e); } })(); }; /// An async wrapper around `setTimeout()` and `setInterval()`. /// /// # `setTimeout()` /// `await (new AsyncTimeout(() => "value", 100)).timeout() /* === "value" */` is `setTimeout(()=> "value", 100)` promisified. /// /// # `setInterval()` /// `for await (const value of new AsyncTimeout(() => "value", 100)).interval()) { /* value === "value" */ }` is `setInterval(()=> "value", 100)` promisified. /// This is an infinite iterator. To cancel the interval, simply break out of the `for await` loop. /// /// # Parameters /// * `thing` - The function to call after the timeout/interval. The result of the promise (or the yield for `interval()`) is the result of this function call. If the funtion throws, then the rejection of the promise will be that error thrown. /// * `interval` - The time to wait for the timeout or interval /// /// # Examples /// See `examples` below. function AsyncTimeout(thing, interval) { this.timeout = () => new Promise((resolve, reject) => { setTimeout(() => { const eval_thing = () => { try { return thing(); } catch(e) { reject(e); } }; const rv = eval_thing(); if(is_promise(rv)) fire_and_forget(async () => resolve(await rv), reject); else resolve(rv); }, interval); }); this.interval = async function*() { while(true) { yield await new Promise((resolve, reject) => { const eval_thing = () => { try { return thing(); } catch(e) { reject(e); } }; setTimeout(() => { const irv = eval_thing(); if(is_promise(irv)) fire_and_forget(async() => resolve(await irv), reject); else resolve(irv); }, interval); }); } }; } /// Return a promise that will resolve when the timeout `i` has completed. There is no value for this resolution. AsyncTimeout.await_timeout = (i) => new AsyncTimeout(() => {}, i).timeout(); /// Return an async iterator that will resolve the next iteration every `i`. There is no value for these yielded resolutions. AsyncTimeout.await_interval = (i) => new AsyncTimeout(() => {}, i).interval(); /// Cancellation token source for `timeout_example` function Cancellation() { let tokens = []; this.cancel_after_interrupt = (sig) => new Promise(resolve => { sig = sig || 'SIGINT'; process.on(sig, () => { //console.log(`>>> Received signal ${sig}`); resolve(sig || ""); }); }); this.cancel_after_invoke = () => new Promise(resolve => { tokens.push(resolve); }); this.cancel_invoke = (value) => tokens.shift()(value || ""); } /// Run the timeout example const timeout_example = async (_timeout, cancelAfter) => { _timeout = _timeout || 100; let cancel = false; const canceller = new Promise(resolve => { if(is_promise(cancelAfter)) (async () => { resolve(cancel = ((await cancelAfter) || true)); })(); else if(cancelAfter && cancelAfter > 0) setTimeout(() => resolve(cancel = true), cancelAfter); else resolve(false); }); // Wait _timeout then alert "hi" console.log(await (new AsyncTimeout(() => "hi", _timeout)).timeout()); // Wait _timeout then call then await this async lambda, then print its result. console.log(await (new AsyncTimeout(async () => { console.log("> Hi starting promise..."); await AsyncTimeout.await_timeout(_timeout); console.log(">> Hi inside promise!"); await AsyncTimeout.await_timeout(_timeout); console.log("> Hi ending promise..."); return "hi, from async lambda!"; }, _timeout)).timeout()); // Wait _timeout as with this async lambda which uses `.interval()` producing the result `(_timeout / 10) || 10` times. console.log(await (new AsyncTimeout(async () => { let i=0; const len = (_timeout / 10) || 10; for await (const value of AsyncTimeout.await_interval(_timeout)) { console.log(`[al]: iteration ${i}`); i+=1; if(cancel) throw "Operation cancelled."; else if(i == len) return `Hi from async lambda containing iterator!, Completed ${i} iterations`; } }, _timeout)).timeout()); // Wait _timeout as interval running at most `(_interval / 10) || 10` times console.log(await (async () => { let i=0; const len = (_timeout / 10) || 10; for await (const value of new AsyncTimeout(async () => { console.log(`i > Waiting ${_timeout * 2}`); await AsyncTimeout.await_timeout(_timeout * 2); console.log(`i > Returning ${i} as iteration value.`); return `${i}`; }, _timeout).interval()) { console.log(`!i >>> Iteration ${i}`); i += 1; if(cancel) throw "Operation cancelled."; else if(i == len) return value; } })()); // Continuously wait _timeout then alert "hi forever", until `cancelAfter` is reached (if it is) for await (const value of new AsyncTimeout(() => "hi forever", _timeout).interval()) { if(cancel) break; console.log(value); } // Return `true` if cancelled (or, if `cancelAfter` was a promise that returned a truthy value, that resolved value); `false` if not (should never happen) return await canceller; }; /// Run the timeout example, catching an error if there is one thrown (usually on cancellation.) const try_example = async (time) => { try { return { value: await timeout_example(null, time) }; } catch(e) { console.log(`Error! ${e}`); return {error: e}; } }; //try_example(2500); // Error! Operation cancelled. -> `{ error: "Operation cancelled." }` //try_example(5000); // -> `{ value: true }`