How can I do I cancel javascript await sleep? - javascript

The most common implementation of a sleep function in javascript is returning a Promise after setTimeout resolves:
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
I have for loop with await sleep to keep it from executing too fast, such as not requesting xhr too fast. I also have a isBreak flag elsewhere to tell me when to stop the for loop. However, the issue I have is that when I break the for loop, the previous await sleep has already executed and is holding up the for loop. Is there a better way of breaking the for loop and also terminating the await sleep instantaneously?
const items = [];
let isBreak = false; // Somewhere else in the application
for (const item of items) {
if (isBreak) break;
// Do something, like xhr request
await sleep(15000); // 15 seconds sleep
if (isBreak) break;
}
Is there a way for me to signal for early

In JS, when an await operation starts, it can no longer be interrupted; it will wait until its operand promise is settled.
So, you have to make the promise you're awaiting cancelable in some way.
Unfortunately, your code can't get notified about a variable reassignment (when you set isBreak to true), and polling it would be inefficient.
Instead of a flag, you could use an AbortSignal (which was invented for this purpose), and make your sleep accept one:
function sleep(ms, signal) {
return new Promise((resolve, reject) => {
signal.throwIfAborted();
const timeout = setTimeout(() => {
resolve();
signal.removeEventListener('abort', abort);
}, ms);
const abort = () => {
clearTimeout(timeout);
reject(signal.reason);
}
signal.addEventListener('abort', abort);
});
}
Then, you use it like this:
const items = [];
const isBreak = new AbortController(); // Somewhere else in the application, call `isBreak.abort()`
try {
for (const item of items) {
// Do something, like xhr request
await sleep(15000, isBreak.signal); // 15 seconds sleep
}
} catch (e) {
if (e.name === 'TimeoutError') {
// Handle a cancellation
console.log('Cancelled');
} else {
// Not a cancellation, rethrow it
throw e;
}
}
An AbortSignal works well with fetch as well, in case you have to cancel that too.

An answer i found in a blog in the past that i adjusted. It is similiar to FZs answer. Same usage, too. Just to give an alternative.
relevant too: How to cancel timeout inside of Javascript Promise?
function sleep(ms, abortSignal) {
return new Promise((resolve, reject) => {
signal.addEventListener("abort", abort);
if(abortSignal.aborted){
abort();
}
const timeout = setTimeout(end, ms);
function abort() {
clearTimeout(timeout);
abortSignal.removeEventListener("abort", abort);
reject(new Error("sleep aborted"));
}
function end() {
abortSignal.removeEventListener("abort", abort);
resolve();
}
});
}

Related

Wait until promise returns true

I have a database setup with NodeJS and want to wait until certain table is created before start to create any others. This method tableExists resolves with the status being either true/false, but I want it to wait until it's true only.
const checkTableExists = async () => {
const exists = await queryInterface.tableExists('Subjects');
return exists;
}
How can I force a wait until checkTableExists returns true?
Using setTimeout:
const CHECK_INTERVAL = 200; // every 200ms
const checkTableExists = async () => {
const exists = await queryInterface.tableExists('Subjects');
if (!exists) {
return new Promise((resolve, reject) => {
setTimeout(() => checkTableExists().then(resolve).catch(reject), CHECK_INTERVAL);
});
}
return exists;
}
The solution to something like this is not to keep on waiting. There are other issues that may cause the table not to be created. You may want to adjust the above code to stop checking after it has checked for set number of times, or a duration has passed. Use something reasonable, depending on the environment where your db is running.
Add a delay and repeat:
// Utility function
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const checkTableExists = async () => {
while (true) {
const exists = await queryInterface.tableExists('Subjects');
if (exists) return true;
await delay(10000); // Wait 10 seconds before trying again.
}
}
Although this resolves the promise with true, it is actually is not necessary to return a boolean, as the resolving promise is enough as a signal that the table now exists -- true is the only possible outcome when the promise resolves.

Notification.requestPermission promise is never resolved if user has quieter messaging enabled

We have a slider button on our site where users can opt in to browser push notifications. We recently noticed that the chromium feature "quieter messaging" causes some issues in our implementation. We always evaluated the returned promise of requestPermission to tell the user if their action actually worked. However, if the setting is enabled, the promise is never resolved(or rejected) unless the user clicks on "allow".
async function init() {
var permission = await Notification.requestPermission();
alert(permission); // This is never called in this case
}
init();
We want to tell the user if their action (enabling the notifications) actually worked, or if they need to check their browser settings. Is there a better way than having a separate timeout promise?
This is my current best guess for a workaround:
// https://italonascimento.github.io/applying-a-timeout-to-your-promises/
const promiseTimeout = function (ms, promise) {
// Create a promise that rejects in <ms> milliseconds
let timeout = new Promise((resolve, reject) => {
let id = setTimeout(() => {
clearTimeout(id);
reject('Timed out in ' + ms + 'ms.')
}, ms)
})
// Returns a race between our timeout and the passed in promise
return Promise.race([
promise,
timeout
])
}
async function init() {
var permissionPromise = Notification.requestPermission();
try {
var p = await promiseTimeout(1000/*some magic number*/, permissionPromise);
alert(p);
} catch (error) {
alert(error);
}
}
init();
Posting my workaround as an answer since there seems to be no better other solution at this point.
// https://italonascimento.github.io/applying-a-timeout-to-your-promises/
const promiseTimeout = function (ms, promise) {
// Create a promise that rejects in <ms> milliseconds
let timeout = new Promise((resolve, reject) => {
let id = setTimeout(() => {
clearTimeout(id);
reject('Timed out in ' + ms + 'ms.')
}, ms)
})
// Returns a race between our timeout and the passed in promise
return Promise.race([
promise,
timeout
])
}
async function init() {
var permissionPromise = Notification.requestPermission();
try {
var p = await promiseTimeout(1000/*some magic number*/, permissionPromise);
alert(p);
} catch (error) {
alert(error);
}
}
init();

retrieve logs from server while performing a "parallel" promise.all( request to the server) (parallel challenge!)

Arriving with a theory question :)
I have a front that sends (axios) N requests in a Promise.all() with a map function. This works fine. Each time one of the promises is good, I have a little table that gets updated with each request's answer until I get the full table and the array of the answers at the end. ✅
The problem comes when I want to read, at the same time, the logs of the server
So my objective is to run another axios request to my express.js server that will run each 2 seconds to retrieve the logs of the last 2 seconds, this way I could show the logs of what is happening with each answer in real time.
Any ideas of how doing this two tasks in parallel?
In the front I'm using react and the promise.All has this is structure:
setIsLoading(true); // setting a flag to know this is running
const doAllTheTable = await Promise.all(
tableData.map(async (lineOfMyTable) => {
const answer = await doMyRequest(lineOfMyTable) // my axios.get request
return updateTableLine(answer) // the functions that update the good line
})
);
//all promises are good now
setIsLoading(false)
So, basically I want to have another loop that runs each 2 seconds while "isLoading" is true to update another part of my front and show the logs meanwhile. But I need both things to happen at the same time!
Thank you for your ideas :)
Rather than awaiting your Promise.all immediately, store a reference to the promise so you can start checking the logs:
const doAllTheTablePromise = Promise.all(
tableData.map(async lineOfMyTable => {
const answer = await doMyRequest(lineOfMyTable); // my axios.get request
return updateTableLine(answer); // the functions that update the good line
});
);
let cancelled = false;
(async () => {
while (!cancelled) {
// Check your logs..
await new Promise(r => setTimeout(r, 2000)); // 2 second delay
}
})();
await doAllTheTablePromise;
cancelled = true;
Once your doAllTheTablePromise has resolved, you can stop checking the logs.
Must be many ways to write this. Here's one involving a token provided by the caller of two async processes, foo() and bar(), for communication between them.
async function foo(tableData, token) {
try {
await Promise.all(tableData.map(async (lineOfMyTable) => {
const answer = await doMyRequest(lineOfMyTable);
return updateTableLine(answer);
}));
token.setIsLoading = false; // lower flag when all requests are complete
} catch(error) {
token.setIsLoading = false; // lower flag if synchronous or asynchronous error occurs
}
}
async function bar(token) {
function delay(ms) { // this can be written as inner or outer function, whichever suits.
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
if(token.setIsLoading) {
let logs = await retrieveLogs();
// process/display logs here
await delay(2000);
return bar(token); // recursive call
} else {
return "complete"; // optional
}
}
async function myCaller() {
// ... preamble
let loadingToken = { // passed to foo() and bar() as a means of communication between them.
'setIsLoading': true // raise flag before calling foo() and bar().
};
return Promise.all(foo(tableData, loadingToken), bar(loadingToken));
}
EDIT:
Maybe better written like this, with the caller looking after lowering the flag:
async function foo(tableData) {
return Promise.all(tableData.map(async (lineOfMyTable) => {
return updateTableLine(await doMyRequest(lineOfMyTable));
}));
}
async function bar(token) {
function delay(ms) { // this can be written as inner or outer function, whichever suits.
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
if(token.setIsLoading) {
let logs = await retrieveLogs();
// process/display logs here
await delay(2000);
return bar(token); // recursive call
} else {
return "complete"; // optional
}
}
async function myCaller() {
// ... preamble
let loadingToken = { // passed to bar().
'setIsLoading': true // raise flag before calling foo() and bar().
};
return Promise.all(
foo(tableData).finally(() => { loadingToken.setIsLoading = false }),
bar(loadingToken)
);
}

How can I clear timeout for an asynchronous function in JavaScript

Let me be very clear so as to avoid confusion.
I have a sleep function (below) which timeouts for as many millisecond as I specify.
function sleep(ms)
{
return new Promise(resolve => setTimeout(resolve, ms));
}
Now in another function beginTest() I called sleep for some millisecond let say 5000 ms.
async function beginTest()
{
await sleep(5000);
}
I have a separate function which gets executed when a button is clicked. I want the function to clear the timeout whenever the button is clicked before completion of 5000 ms. I understand that after 5000 ms clicking the button should not affect anything as the promise has already been resolved.
document.getElementById("reactTimeClick").onmousedown = function()
{
cleartimeOut(); // Clear timeout of above function
}
It's pretty simple to do as you propose with a "cancellation token" via which the "sleep timeout" can be cancelled:
// don't do this
function sleep(ms, cancellationToken) {
return new Promise(resolve => function() {
let timeoutRef = setTimeout(resolve, ms);
cancellationToken.cancel = function() {
clearTimout(timeoutRef);
};
});
}
However that will not, in itself, cause the sleep promise to depart from its pending state and the promise chain (or awaiting statements) stemming from sleep() will not be informed of the cancellation. Progress will hang.
This shortcoming can be overcome, again with a cancellation token, but this time one that allows the sleep() promise to be rejected, as follows ...
// do this
function sleep(ms, cancellationToken) {
return new Promise((resolve, reject) => function() {
cancellationToken.cancel = function() {
reject(new Error('sleep() cancelled'));
};
setTimeout(resolve, ms));
}
}
... and in the caller, use as follows:
async function beginTest() {
try {
const token = {};
const promise = sleep(5000, token);
$('#cancelButton').on('click', token.cancel); // jQuery example, or similar in POJS
await promise;
// ... test code ...
// ... return whatever;
}
catch(error) {
console.log(error.message);
// If button was clicked before the 5000 ms has expired,
// and no other error has been thrown,
// then the log will show "sleep() cancelled".
throw error; // rethrow error to keep beginTest's caller informed.
}
}

While Loop inside ASYNC AWAIT

I have some code that continuously updates a series of objects via network calls looks like this. I was wondering if this is bad practice and if there might be a better way. I cant use Set Interval as the time between MakeAsyncCall replies is variable and can cause a leak if the time to make the call is longer than the delay. I will be using this info to update a UI. Will this cause blocking? What are your thoughts? Let me know if you need more info.
let group = [item1, item2, item3];
// Start Loop
readForever(group, 100);
// Function to Delay X ms
const delay = ms => {
return new Promise((resolve, _) => {
const timeout = setTimeout(() => {
resolve();
}, ms);
});
};
// Function to continuously Make Calls
const readForever = async (group, ms) => {
while(true) {
// Make Async Call
for (let item of group) {
await MakeAsyncCall(item);
}
// Wait X ms Before Processing Continues
await delay(ms);
}
};
The given code won't cause any UI blocking. And is a valid way to update the UI continually.
Instead of a loop you could write it that way:
const readForever = async (group, ms) => {
// Make Async Call
for (let item of group) {
await MakeAsyncCall(item);
}
// Wait X ms Before Processing Continues
await delay(ms);
if (true) { // not needed, but there you could define an end condition
return readForever(group, ms);
}
};
In addition to the comment about the delay function:
You could directly pass the resolve to setTimeout, and because you do not cancel the Timeout anywhere you do not need to store the result setTimeout in a variable.
const delay = ms => {
return new Promise(resolve => setTimeout(resolve, ms));
};

Categories

Resources