javascript - cancel awaited function before it is called again - javascript

On incoming webrtc call, I open a modal to show user a message about media permissions and await this till the user presses OK button on the modal.
I do it like this:
async function myMessage(){
$('#id_my_message_modal').modal('show');
return new Promise(resolve =>
$('#id_my_message_button').on('click', () => {
$('#id_my_message_modal').modal('hide');
resolve();
}
)
);
}
then
await myMessage();
The issue I am facing now is if await myMessage(); is called again while the previous call has still not returned(i.e user hasn't pressed OK button). I want a way to cancel any previous await myMessage();, if exists, before it is called again.
Is there any way to do it?

The first approach (Live demo)- add every call of the async function to the queue, so you will get a sequence of dialogs with the result returning (close/accept/whatever).
// decorator from my other answer
function asyncBottleneck(fn, concurrency = 1) {
const queue = [];
let pending = 0;
return async (...args) => {
if (pending === concurrency) {
await new Promise((resolve) => queue.push(resolve));
}
pending++;
return fn(...args).then((value) => {
pending--;
queue.length && queue.shift()();
return value;
});
};
}
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const myMessage = asyncBottleneck(async function () {
const $modal = $("#id_my_message_modal");
$modal.modal("show");
const result = await new Promise((resolve) =>
$modal.on("click", "button", (e) => {
$modal.modal("hide");
$modal.off("click", "button");
resolve($(e.target).data("action"));
})
);
await delay(250);
return result;
});
The second approach (Live demo)- multiplexing the fn calls, when every await of the function will return the same result; Compare console output of the live demos.
function singleThread(fn) {
let promise = null;
return function (...args) {
return (
promise ||
(promise = fn(...args).finally(() => {
promise = null;
}))
);
};
}
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const myMessage = singleThread(async function () {
const $modal = $("#id_my_message_modal");
$modal.modal("show");
const result = await new Promise((resolve) =>
$modal.on("click", "button", (e) => {
$modal.modal("hide");
$modal.off("click", "button");
resolve($(e.target).data("action"));
})
);
await delay(250);
return result;
});
$("#btn-run").on("click", async () => {
myMessage().then((c) => console.log(`First result: ${c}`));
await myMessage().then((c) => console.log(`Second result: ${c}`));
myMessage().then((c) => console.log(`Third result: ${c}`));
});
The third way- closing the previous modal with its promise rejecting (Live Demo open console there to see the result).
import CPromise from "c-promise2";
const showModal = (() => {
let prev;
return (id, text = "") => {
prev && prev.cancel();
return (prev = new CPromise((resolve, reject, { onCancel }) => {
const $modal = $(id);
text && $modal.find(".modal-body").text(text);
$modal.modal("show");
const dispose = () => {
$modal.modal("hide");
$modal.off("click", "button");
};
$modal.on("click", "button", function (e) {
dispose();
resolve($(this).data("action"));
});
$modal.on("hidden.bs.modal", () => {
setTimeout(() => resolve("close"));
});
onCancel(dispose);
})).finally(() => (prev = null));
};
})();
$("#btn-run").on("click", async () => {
showModal("#id_my_message_modal", "First message").then(
(c) => console.log(`First modal result: ${c}`),
(e) => console.warn(`First modal fail: ${e}`)
);
showModal("#id_my_message_modal", "Second message").then(
(c) => console.log(`Second modal result: ${c}`),
(e) => console.warn(`Second modal fail: ${e}`)
);
const promise = showModal("#id_my_message_modal", "Third message")
.then(
(c) => console.log(`Third modal result: ${c}`),
(e) => console.warn(`Third modal fail: ${e}`)
)
.timeout(5000)
.then(() => {
return showModal(
"#id_my_message_modal2",
"Blue Pill or Red Pill?"
).then((v) => console.log(`Pill: ${v}`));
});
/*setTimeout(()=>{
promise.cancel(); you can cancel the modal from your code
}, 1000);*/
});

Related

How to write await() for multiple promises in JavaScript?

I m trying to write await for multiple promises, instead of nested functions.
Please take a look at the below code i tried, as it will explain better than me.
var main = async () => {
// const main_ = await Promise.all(fun1,fun2,fun3);
// Fun 3
const fun3 = () =>
new Promise((resolve) => async () => {
// console.log(1);
return resolve(await fun2(1));
});
// Fun 2
const fun2 = (value) =>
new Promise((resolve) => async (value) => {
value = value + 1;
// console.log(value);
return resolve(await fun1(value));
});
// Fun 1
const fun1 = () =>
new Promise((resolve) => (value) => {
value = value + 1;
// console.log(value);
return resolve(value);
});
fun3();
};
main();
I tried console logging to debut but I m getting nothing in the console.
Any help is greatly appreciated.
the syntax is wrong, its not new Promise((resolve) => async () => {}) with 2 arrow, but new Promise((resolve) => {}) also you can call promise function without await
var main = async () => {
// const main_ = await Promise.all(fun1,fun2,fun3);
// Fun 3
const fun3 = () => new Promise(resolve => {
//console.log(1);
return resolve(fun2(1));
});
// Fun 2
const fun2 = (value) => new Promise(resolve => {
value = value + 1;
//console.log(value);
return resolve(fun1(value));
});
// Fun 1
const fun1 = (value) => new Promise(async (resolve) => {
value = value + 1;
console.log('sleep 3 seconds');
await new Promise(r => setTimeout(r, 3000));
console.log(value);
return resolve(value);
});
fun3();
};
main();
If you want to call await inside a Promise callback, you can do this:
const p = new Promise((resolve) => {
(async () => {
const res = await anotherPromise();
resolve(res);
})();
});
So with this in mind, you can rewrite your functions like this:
var main = async () => {
// const main_ = await Promise.all(fun1,fun2,fun3);
// Fun 3
const fun3 = () =>
new Promise((resolve) => {
(async () => {
resolve(await fun2(1));
})();
});
// Fun 2
const fun2 = (value) =>
new Promise((resolve) => {
value = value + 1;
(async () => {
resolve(await fun1(value));
})();
});
// Fun 1
const fun1 = (value) =>
new Promise((resolve) => {
value = value + 1;
(async () => {
resolve(value);
})();
});
return await fun3();
};
// ans: 3
console.log(await main());
If you want to do it in pure async/await, you may do this:
const main = async () => {
const fun1 = async (value) => value + 1;
const fun2 = async (value) => await fun1(value + 1);
const fun3 = async () => await fun2(1);
return await fun3();
}
// output: 3
console.log(await main());
I hope this example works for you.
console.clear();
function wait(ms, data) {
return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}
/**
* These will be run in series, because we call
* a function and immediately wait for each result,
* so this will finish in 1s.
*/
async function series() {
return {
result1: await wait(500, 'seriesTask1'),
result2: await wait(500, 'seriesTask2'),
}
}
/**
* While here we call the functions first,
* then wait for the result later, so
* this will finish in 500ms.
*/
async function parallel() {
const task1 = wait(500, 'parallelTask1');
const task2 = wait(500, 'parallelTask2');
return {
result1: await task1,
result2: await task2,
}
}
async function taskRunner(fn, label) {
const startTime = performance.now();
console.log(`Task ${label} starting...`);
let result = await fn();
console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result);
}
void taskRunner(series, 'series');
void taskRunner(parallel, 'parallel');
fun3() is an async function so you have to put await before the call.
var main = async () => {
// Fun 3
const fun3 = () => new Promise(resolve => {
//console.log(1);
return resolve(fun2(1));
});
// Fun 2
const fun2 = (value) => new Promise(resolve => {
value = value + 1;
//console.log(value);
return resolve(fun1(value));
});
// Fun 1
const fun1 = (value) => new Promise(async (resolve) => {
value = value + 1;
console.log('sleep 3 seconds');
await new Promise(r => setTimeout(r, 3000));
console.log(value);
return resolve(value);
});
await fun3();
};
main();

Proper use of async JS to ensure function calls wait for previous functions to complete (resolve?)

Trying to learn proper async/await JavaScript to run functions in sequence when an early function in the sequence would be delayed (using setTimeout to simulate). I'm not getting the expected results (still getting "first", "second", "this should run first?" - see code).
What am I missing? Do I have the wrong idea about this?
Thanks in advance!
const zeroFunction = () => {
setTimeout(() => {
return new Promise((resolve) => {
console.log("This should run first?");
resolve();
});
}, 2000)}
const firstFunction = () => {
return new Promise((resolve) => {
console.log("first");
resolve();
})
}
const secondFunction = () => {
return new Promise((resolve) => {
console.log("second");
resolve();
})
}
async function fnAsync() {
await zeroFunction();
await firstFunction();
secondFunction();
}
fnAsync();
zeroFunction is currently returning undefined implicitly, not a Promise. Inverse the wrapping of the setTimeout and Promise constructor and it should work as expected.
const zeroFunction = () => {
return new Promise((resolve) => {
setTimeout(() => {
console.log("This should run first?")
resolve()
}, 2000)
})
}
const firstFunction = () => {
return new Promise((resolve) => {
console.log("first")
resolve()
})
}
const secondFunction = () => {
return new Promise((resolve) => {
console.log("second")
resolve()
})
}
async function fnAsync() {
await zeroFunction()
await firstFunction()
secondFunction()
}
fnAsync()

What is this function doing in JavaScript?

I'm learning JavaScript and I'm learning about Promises. I'm having a hard time to understand the following code:
const button = document.querySelector("button");
const div = document.querySelector("div");
const setText = (text) => {
div.textContent = text
}
const checkAuth = () => {
return new Promise((resolve, reject) => {
setText('Checking Auth...')
setTimeout(() => {
resolve(true);
}, 2000);
});
};
const fetchUser = () => {
return new Promise((resolve, reject) => {
setText('Fetching User...');
setTimeout(() => {
resolve({ name: "Max" });
}, 2000);
});
};
button.addEventListener("click", () => {
checkAuth()
.then(
isAuth => {
if (isAuth) {
return fetchUser()
}
}
)
.then(
user => {
setText(user.name)
}
)
});
I understand the first two functions checkAuth and fetchUser, they return a resolved Promise after 2 seconds.
I can't really understand how does the event listener of button work. I see it calls checkAuth() and when executed, the div text of the HTML document changes to Checking Auth... for two seconds (until the function resolves successfully the Promise. When resolved, it enters into the .then() part of the code.
I'm struggling to understand where does isAuth and user come from. I feel like they are some implicit declaration of a function (called inside themselves?), it's looking really odd to me.
checkAuth() returns a promise and the value with which it resolves is passed to the callback function of checkAuth().then(...) which is then assigned to isAuth parameter.
In the case of user, it is the value with which the promise returned by checkAuth().then() is resolved. If the callback function of checkAuth().then(...) returns a non-promise value, user will be that non-promise value.
If the callback function of checkAuth().then() returns a promise, then user will be the value with which that promise, returned by the callback function, resolves.
Here's a simplified example that might help you understand how isAuth and user are assigned a value:
Promise.resolve(true)
.then(isAuth => {
console.log("isAuth: " + isAuth);
return { name: "John Doe" };
})
.then(user => console.log(user));
Here is the explanation:
const button = document.querySelector("button");
const div = document.querySelector("div");
const setText = (text) => {
div.textContent = text
}
const checkAuth = () => {
return new Promise((resolve, reject) => {
setText('Checking Auth...')
setTimeout(() => {
resolve(true); // The return value when the promise resolves which is in this case is `true`;
}, 2000);
});
};
const fetchUser = () => {
return new Promise((resolve, reject) => {
setText('Fetching User...');
setTimeout(() => {
resolve({ name: "Max" }); // Here when the promise resolves it is returning `{ name: "Max" }`;
}, 2000);
});
};
button.addEventListener("click", () => {
checkAuth()
.then(isAuth => { // This is an arrow function, you can see we are accepting the `isAuth` parameter;
if (isAuth) { // Regular if...
return fetchUser() // And the this also returns an promise
}
}
)
.then(user => { // Which is resolved here, And the user is the `{ name: "Max" }`;
setText(user.name)
}
)
});
isAuth is returned by checkAuth and user is returned by fetchUser.
I'll re-write this in async/await format. You can compare to make better sense of it.
const delay = (time) => new Promise(res => setTimeout(res(), time));
const checkAuth = async () => {
setText('Checking Auth...');
await delay(2000);
}
const fetchUser = async () => {
setText('Fetching User...');
await delay(2000);
}
const executeOrder = async () => {
const isAuth = await checkAuth();
if (isAuth) {
const user = await fetchUser();
setText(user.name)
}
}
button.addEventListener("click", executeOrder);

Promise resolving once

Promise in my code is resolving only at first execution, it is simple form submission with reCAPTCHA verification. From debugging I know the browser interpreter reaches line with await captchaVerification() and stops there. First execution works without any errors.
contactForm.addEventListener('submit', async (e) => {
e.preventDefault();
await captchaVerification()
const data = new FormData(contactForm)
_alert.innerHTML = 'Sending message...'
_alert.setAttribute('class', `alert show`);
ajax(contactForm.method, contactForm.action, data)
.then(([r, t]) => outcome(r ? r = 'success' : r = 'error', t))
.catch(e => outcome('error', e))
});
Full context on hastebin: https://hastebin.com/oyuvoyeluv.js
From the link you posted I can see that in you captchaVerification function, you check if a captcha has previously been rendered, if not you render and resolve or reject the promise. The problem is you never resolve or reject if isRendered is true.
function captchaVerification() {
return new Promise((resolve, reject) => {
captcha.style.display = 'block';
const verifyCallback = (response) => {
if (response) {
captcha.style.display = 'none'
grecaptcha.reset()
return resolve(response)
} else {
grecaptcha.reset()
return reject('Invalid captcha verification.')
}
}
// The problem is here as you do nothing if the captcha was
// previously rendered.
if (!isRendered) {
grecaptcha.render('g-recaptcha', {
'sitekey': 'my-secret-key',
'theme': 'dark',
'callback': verifyCallback
})
isRendered = true;
}
})
}
Based on the code you have posted you have the following options:
1st option use then
contactForm.addEventListener('submit', (e) => {
e.preventDefault();
captchaVerification().then((response) => {
// check if response is ok
if(response ... is not what expected end here) {
return false;
}
const data = new FormData(contactForm)
_alert.innerHTML = 'Sending message...'
_alert.setAttribute('class', `alert show`);
ajax(contactForm.method, contactForm.action, data)
.then(([r, t]) => outcome(r ? r = 'success' : r = 'error', t))
.catch(e => outcome('error', e))
});
});
2nd option use await
contactForm.addEventListener('submit', async (e) => {
e.preventDefault();
let response = await captchaVerification();
// check if response is ok
if(response ... is not what expected end here) {
return false;
}
const data = new FormData(contactForm)
_alert.innerHTML = 'Sending message...'
_alert.setAttribute('class', `alert show`);
ajax(contactForm.method, contactForm.action, data)
.then(([r, t]) => outcome(r ? r = 'success' : r = 'error', t))
.catch(e => outcome('error', e))
});
You could also try changing two-three lines in your hastebin example.
var isRendered = false;
async function captchaVerification() {
return await new Promise((resolve, reject) => {
captcha.style.display = 'block'
const verifyCallback = (response) => {
if (response) {
captcha.style.display = 'none'
grecaptcha.reset()
return resolve(response)
} else {
grecaptcha.reset()
return reject('Invalid captcha verification.')
}
}
if (!isRendered) {
grecaptcha.render('g-recaptcha', {
'sitekey': 'my-secret-key',
'theme': 'dark',
'callback': verifyCallback
})
isRendered = true;
}
})
}
It purely depends on captchaVerification implementation. If function return the same promise. It won't resolve next time. It is like Singelton. so better create new promise always.
const promise = new Promise(r => setTimeout(r, 100, Math.random()))
const captchaVerification = () => promise;
const captchaVerificationRe = () => new Promise(r => setTimeout(r, 100, Math.random()));
async function main() {
let res = await captchaVerification();
console.log(res); // 0.3251446189302283
res = await captchaVerification("sffsfs");
console.log(res); // 0.3251446189302283
res = await captchaVerificationRe();
console.log(res); // 0.06299211055753262
res = await captchaVerification("sffsfs");
console.log(res); // 0.721527810718094
}
main();
For everyone wondering, this is the piece of code that solved my issue.
Basically Google API does not allow user to render same captcha object on same HTML Element more than once. I solved it by dynamically creating HTML Element for my captcha and removing it after I receive the response from the Google API server-side verification (success/error).
https://gist.github.com/ANTOSzbk/75ed7003e914162550f61399122a3bd4

mergePromise(...).then is not a function

When I run the code, I get the error
mergePromise(...).then is not a function.
I want to know why I got this error.
const timeout = ms => new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, ms);
});
const ajax1 = () => timeout(2000).then(() => {
console.log('1');
return 1;
});
const ajax2 = () => timeout(1000).then(() => {
console.log('2');
return 2;
});
const ajax3 = () => timeout(2000).then(() => {
console.log('3');
return 3;
});
const mergePromise = ajaxArray => {
const data=[];
ajaxArray[0]().then(i=>data.push(i));
timeout(1005).then(() => {
ajaxArray[1]().then(i=>data.push(i));
});
timeout(10).then(() => {
ajaxArray[2]().then(i=>data.push(i));
});
return data;
};
I guess maybe the timeout function has some mistake. What am I doing wrong?
Maybe you can try return Promise.resolve(data) instead, it would return a promise, which is what you need.

Categories