You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
5.5 KiB
153 lines
5.5 KiB
|
|
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 || "<signal>");
|
|
});
|
|
});
|
|
|
|
|
|
this.cancel_after_invoke = () => new Promise(resolve => {
|
|
tokens.push(resolve);
|
|
});
|
|
|
|
this.cancel_invoke = (value) => tokens.shift()(value || "<invoked>");
|
|
}
|
|
|
|
/// 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 }`
|