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();
Related
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();
}
});
}
I have a function that would return a promise, and in the case of an error, I have to call the same function again. The problem is that whenever I call it again, I get the same response, as if it was never called again.
This is how am resolving:
first_file = async () => {
return new Promise(async (resolve, reject) => {
//Generating the token
(async () => {
while (true) {
console.log("Resolving...");
resolve(token);
await sleep(5000);
resolved_token = token;
}
})();
});
};
I'm generating a token here, which I use in the second script:
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
(async() =>{
while(true){
test = require("./test")
test.first_file ().then(res=>{
console.log(res)
})
await sleep(15000)
}
})()
The expected value here is that every 15000ms (15 sec) I get a new response, but here I'm getting the same response over and over again.
Sorry if the title is inaccurate; I didn't know how to explain the problem.
Promises represent a value + time, a promise's settled value doesn't change like the number 5 doesn't change. Calling resolve multiple times is a no-op*.
What you want to do instead of using the language's abstraction for value + time is to use the language's abstraction for action + time - an async function (or just a function returning a promise)
const tokenFactory = () => {
let current = null;
(async () =>
while (true) {
console.log("Resolving...");
current = token; // get token somewhere
await sleep(5000);
}
})().catch((e) => {/* handle error */});
return () => current; // we return a function so it's captured
};
Which will let you do:
tokenFactory(); // first token (or null)
// 5 seconds later
tokenFactory(); // second token
*We have a flag we added in Node.js called multipleResolves that will let you observe that for logging/error handling
First thing to note I'm using node version of 6.15.
Here I have a promise method which will return response upon calling an function.
myFunction: (params) = {
return innerFunction(params)
.then((resp) => {
return resp })
.catch((err) => {
throw err })
}
Here the challenge is innerFunction sometime will take more time to give response, so I need to return this function as an error if response doesn't received in 1 minute
How can I achieve that?
This is simply achieved with static method Promise.race(). All you have to do is race the promise returned by innerFunction() against a promisified timeout that settles to an Error.
if innerFunction() wins the race, then its result/Error will be delivered.
if the promisified timeout wins the race, then its Error will be delivered.
In other words, Promise.race() is transparent to whichever promise wins the race and opaque to the other.
myFunction: (params) = {
let timeoutPromise = new Promise((_, reject) => {
setTimeout(reject, 60000, new Error('timed out'));
});
return Promise.race([timeoutPromise, innerFunction(params)]); // race two promises against each other.
}
For flexibility, you might choose to pass the timeout duration to the function, and allow a zero value to mean no timeout.
myFunction: (params, timeoutDuration = 0) = {
if(timeoutDuration > 0) {
let timeoutPromise = new Promise((_, reject) => {
setTimeout(reject, timeoutDuration, new Error('timed out'));
});
return Promise.race([timeoutPromise, innerFunction(params)]); // race two promises against each other.
} else {
return innerFunction(params);
}
}
For a simple solution, I would use the bluebird#timeout method! (more about this here)
Here is a short example, how to use it:
// import the library
const Bluebird = require('bluebird')
// timeout needs the duration parameter in milliseconds
const TIMEOUT_IN_MS = 60000; // 1 min
// if you use the native promise, you must wrap it first
// it returns a promise compatible with the native promise
Promise.resolve(innerFunction(params)).timeout(TIMEOUT_IN_MS)
I'm trying to get a closure to return a value that is supposed to be updated once a promise is resolved (or rejected).
The following code works. Initially the internal variable from within the close returns NONE as expected.
Then the first Promise is launched, and once that is resolved, the internal variable is updated to FAIL.
The second Promise is a deliberate delay, just so that we can observe the change of the closured variable.
However, once the while loop is added to the equation, by uncommenting that loop(x) section, the update is not observable within the while loop.
I would expect to see this:
...
9963000000 NONE
9964000000 NONE
9965000000 NONE
9966000000 NONE
9967000000 NONE
9968000000 FAIL
9969000000 FAIL
9970000000 FAIL
9971000000 FAIL
9972000000 FAIL
9973000000 FAIL
9974000000 FAIL
...
I know it might be due to the single threaded blocking, but, is there a way to observe a dynamic external variable from within the while loop?
let sleep = async (ms) => new Promise ((resolve, reject) => setTimeout (resolve, ms));
let task = async (ms) => new Promise (function(resolve, reject) {
setTimeout (function(){
const error = true;
let result;
if(error){
result = '_NO_';
reject({'state': false, 'response': result});
}else{
result = '_YES_';
resolve({'state': true, 'response': result});
}
}, ms);
});
let loop = async (cb) => {
let i = 0;
while(i<10000000000){
const value = cb.getResponse();
(function() {
if(i%1000000==0){ console.log(i, value) };
i += 1;
})(i, value);
}
}
const linkResponse = (function(){
let response = 'NONE';
function setResponse(value) {response = value; return response};
function getResponse() {return response};
return { 'setResponse': setResponse, 'getResponse': getResponse };
});
const x = linkResponse();
console.log(x.getResponse());
(async () => {
task(3000)
.then(function(res){
console.log('__OK__', res);
let response = 'SUCCESS';
x.setResponse(response)
})
.catch(function(err){
console.log('error', err);
let response = 'FAIL';
x.setResponse(response)
});
sleep(6000)
.then(function(res){
console.log(x.getResponse())
});
//loop(x);
})();
Well, thanks for the help. Just as I was suspecting, it is indeed a blocked thread issue. I solved the problem with a recursive function. I just needed to have a long process running in the background and I naively thought that an infinite loop will do the job.
let loop2 = function(i, cb) {
if(i>100000){
return
}
console.log(i, cb.getResponse());
i += 1;
sleep(0)
.then(function(res){
loop2(i, cb);
});
}
And then calling:
loop2(0, x);
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.
}
}