Related
React app can run node.js function which preparing data and sending information to the database in batches.
It takes a lot of time and I would like to add the ability to stop this function right from react app.
const getShopifyOrders = require('./shopify');
const getTrack = require('./tracking');
const Order = require('./model');
async function addOrdersToDB(limit) {
try {
// Get latest order from DB
let latestOrd = await Order.findOne().sort('-order_number');
do {
// Get Shopify Orders
let orders = await getShopifyOrders(
latestOrd ? latestOrd.order_id : 0,
limit
);
latestOrd = orders[0] ? orders[orders.length - 1] : undefined;
// Update array with tracking status
let fullArray = await getTrack(orders);
// Add to DB
let ins = await Order.insertMany(fullArray, { ordered: false });
console.log(`Added ${ins.length} entries`);
} while (latestOrd);
} catch (err) {
console.log(err);
}
}
module.exports = addOrdersToDB;
I tried a lot of things to include in this function including:
while loop: added the variable outside the function - if 'true' - run code, if not - return - it just doesn't work (variable was changed from react using socket.IO)
setTimeout (also setInterval), triger clearTimeout function from react: this doesn't work as setTimeout and setInterval doesn't work in async function
after that:
made (actually fond here on stackoverflow) new function to promisify setTimeout to be able to use in async function:
const setTimeout2 = (callback, ms) => {
return new Promise(
resolve =>
(to = setTimeout(() => {
callback();
resolve();
}, ms))
);
};
async function addOrdersToDB(limit) {
do {
await setTimeout2(async () => {
try {
// some code here
} catch (err) {
console.log(err);
}
}, 400);
} while (latestOrderExist);
}
function clearTO() {
setTimeout(() => {
console.log('clearTO');
clearTimeout(to);
}, 3000);
}
This for some reason doesn't iterate.
Is there solution for this?
Thanks!
To abort the do/while loop, you will need to add an additional test to that loop that is some variable that can be modified from the outside world. Also, note that the additional test only works here because you're using await inside the loop. If there was no await inside the loop, then the loop would be entirely synchronous and there would be no ability to change a variable from outside the loop while the loop was running (because of nodejs' single-threadedness).
Since this is a server (and globals are generally bad), I will assume we should not use a global. So instead, I would restructure addOrdersToDB() to return a data structure that contains both the promise the existing version returns and an abort() function the caller can call to stop the current processing. This also permits multiple separate calls to addOrdersToDB() to be running, each with their own separate abort() method.
function addOrdersToDB(limit) {
let stop = false;
function abort() {
stop = true;
}
async function run() {
try {
// Get latest order from DB
let latestOrd = await Order.findOne().sort('-order_number');
do {
// Get Shopify Orders
let orders = await getShopifyOrders(
latestOrd ? latestOrd.order_id : 0,
limit
);
latestOrd = orders[0] ? orders[orders.length - 1] : undefined;
// Update array with tracking status
let fullArray = await getTrack(orders);
// Add to DB
let ins = await Order.insertMany(fullArray, { ordered: false });
console.log(`Added ${ins.length} entries`);
} while (!stop && latestOrd);
// make resolved value be a boolean that indicates
// whether processing was stopped with more work still pending
return !!(latestOrd && stop);
} catch (err) {
// log error and rethrow so caller gets error propagation
console.log(err);
throw err;
}
}
return {
promise: run(),
abort: abort
}
}
So, to use this, you would have to change the way you call addOrdersToDB() (since it no longer returns just a promise) and you would have to capture the abort() function that it returns. Then, some other part of your code can call the abort() function and it will then flip the internal stop variable that will cause your do/while loop to stop any further iterations.
Note, this does not stop the asynchronous processing inside the current iteration of the do/while loop - it just stops any further iterations of the loop.
Note, I also changed your catch block so that it rethrows the error so that the caller will see if/when there was an error.
And, the resolved value of the function is the internal stop variable so the caller can see if the loop was aborted or not. A true resolved value means the loop was aborted and there was more work to do.
Here's an additional version of the function that creates more opportunities for it to stop between await operations within your function and within the loop. This still does not abort an individual database operation that may be in progress - you'd have to examine whether your database supports such an operation and, if so, how to use it.
function addOrdersToDB(limit) {
let stop = false;
function abort() {
stop = true;
}
async function run() {
try {
// Get latest order from DB
let latestOrd = await Order.findOne().sort('-order_number');
if (!stop) {
do {
// Get Shopify Orders
let orders = await getShopifyOrders(
latestOrd ? latestOrd.order_id : 0,
limit
);
latestOrd = orders[0] ? orders[orders.length - 1] : undefined;
if (stop) break;
// Update array with tracking status
let fullArray = await getTrack(orders);
if (stop) break;
// Add to DB
let ins = await Order.insertMany(fullArray, { ordered: false });
console.log(`Added ${ins.length} entries`);
} while (!stop && latestOrd);
}
// make resolved value be a boolean that indicates
// whether processing was stopped with more work still pending
return !!(latestOrd && stop);
} catch (err) {
// log and rethrow error so error gets propagated back to cller
console.log(err);
throw err;
}
}
return {
promise: run(),
abort: abort
}
}
I have been referencing the first and second answer from this thread to try and introduce async functions into my program. I am trying to gather user input before building an embed to then send back out to my discord server. I have tried a few different ways but am not making any progress. Here is what I have right now:
///// Lets start here
execute(message, args)
{
embedBuilder(message);
},
};
// Collector to collect the users input and return it to some variable
async function collector(message,limit)
{
message.channel.awaitMessages(response => response.author.id === message.author.id,
{
max: 1,
time: 10000,
errors:['time'],
})
.then((collected) => {
if (collected.first().content.length < limit)
{
message.author.send(`I collected the message : ${collected.first().content}`);
return collected.first().content;
}
//else
collector(limit);
})
.catch(() => {
message.author.send("No message collected after 10 seconds.")
})
}
async function embedBuilder(message)
{
message.author.send("Lets get to work!\nPlease enter the title of your event. (Must be shorter than 200 characters)");
const title = await collector(message,200); // AWAIT HERE
message.author.send("Please enter a short description of your event. (Must be shorter than 2000 characters)");
const description = await collector(message,2000); // AWAIT HERE
const eventEmbed = new Discord.MessageEmbed()
.setColor('RANDOM')
.setTitle(title)
.setAuthor(message.author.username)
.setDescription(description)
.setImage();
message.channel.send(eventEmbed);
}
Right now it is not waiting at all, plowing through both of my prompts to the user and then running 2 collectors at once, so when I type something in, both collectors return the same thing.
For example:
Me : !plan //Prompting the discord command
Bot: Lets get to work!
Please enter the title of your event. (Must be shorter than 200 characters)
Please enter a short description of your event. (Must be shorter than 2000 characters)
Me : Testing
Bot: I collected the message : testing
I collected the message : testing
Can anyone point out what I am doing wrong? I believe I may be misunderstanding something about how async functions work in JS, but I feel like I followed the correct syntax based off of the answers I looked at from the linked post.
Thanks for any help.
You need to return your Promises
async function collector(message,limit)
{
return message.channel....
//else
return collector(limit);
Actually your question related to asynchronous programming in javascript and to implement this you need first understand the way of implementation of them
you have multiple ways to achieve waiting function to execute some code after that function finishes:
using callbacks
In this fashion you can create a function that contains the code you want to be executed after finishing another code
Example:
const func =(...args,cb) => {
console.log('execute this code first')
console.log(4 * 4);
}
const callback = () => {
console.log("call this after finishing the previous code")
}
The second thing is using the new async code in javascript which is called promises, the concept is promising is so simple, you run some code in non-blocking code with the availability to call another function in case of the code success or failing
This can be applied in two ways:
1- then & catch
const successCb = () => {
console.log("success");
}
const failCb = (err) => {
console.log("failed", err)
}
message.channel.awaitMessages().then(successCb).catch(failCb)
2- the second way is using async/await fashion for more cleaner code
async function myFunc() {
try {
await message.channel.awaitMessages();
console.log("process finished, continue execution")
} catch(err) {
console.log("failed")
}
}
In your question, you need firstly specify what the code should be executed first and the code should be executed then, so we have two methods:
collector (called first)
embed builder (executed later)
collector (invalid way)
async function collector(message,limit)
{
message.channel.awaitMessages(response => response.author.id === message.author.id,
{
max: 1,
time: 10000,
errors:['time'],
})
.then((collected) => {
if (collected.first().content.length < limit)
{
message.author.send(`I collected the message : ${collected.first().content}`);
return collected.first().content;
}
//else
collector(limit);
})
.catch(() => {
message.author.send("No message collected after 10 seconds.")
})
}
you have here some conflicts, since you defined the parent function will work in an async way so you need to return a promise inside this method or using await inside it to let the interpreter that this function executes promises inside it but you didn't do that, you use promise. then inside an async function and when you use it inside the embedBuilder function you await this parent function but not await the actual promise, so the promise is executed while another code is also being executed
Collector (valid way)
you have two choices either return promise or using await inside the function as the following:
async function collector(message,limit) {
try {
let collected = await message.channel.awaitMessages(response => response.author.id === message.author.id,
{
max: 1,
time: 10000,
errors:['time'],
})
if (collected.first().content.length < limit)
{
message.author.send(`I collected the message : ${collected.first().content}`);
return collected.first().content;
}
//else
collector(limit);
} catch(err) {
message.author.send("No message collected after 10 seconds.")
}
}
Then you can call it inside the builder as you did
I have javascript function like this:
function myFunction(number) {
var x=number;
...
... more initializations
//here need to wait until flag==true
while(flag==false)
{}
...
... do something
}
The problem is that the javascript is stuck in the while and stuck my program. so my question is how can I wait in the middle of the function until flag is true without "busy-wait"?
Javascript is single threaded, hence the page blocking behaviour. You can use the deferred/promise approach suggested by others. The most basic way would be to use window.setTimeout. E.g.
function checkFlag() {
if(flag === false) {
window.setTimeout(checkFlag, 100); /* this checks the flag every 100 milliseconds*/
} else {
/* do something*/
}
}
checkFlag();
Here is a good tutorial with further explanation: Tutorial
EDIT
As others pointed out, the best way would be to re-structure your code to use callbacks. However, this answer should give you an idea how you can 'simulate' an asynchronous behaviour with window.setTimeout.
You created an infinite loop where the flag value that terminates the loop can never be changed by code outside this loop because no code outside the loop ever gets to run - thus this will never work.
Because javascript in a browser is single threaded (except for webworkers which aren't involved here) and one thread of javascript execution runs to completion before another can run, your statement:
// this won't work!
while(flag==false) {}
will simply run forever (or until the browser complains about a non-responsive javascript loop), the page will appear to be hung and no other javascript will ever get a chance to run, thus the flag's value can never be changed by code outside this loop.
For a little more explanation, Javascript is an event driven language. That means that it runs a piece of Javascript until it returns control back to the interpreter. Then, only when it returns back to the interpreter, Javascript gets the next event from the event queue and runs it.
All things like timers and network events run through the event queue. So, when a timer fires or a network request arrives, it does not ever "interrupt" the currently running Javascript. Instead, an event gets put in the Javascript event queue and then, when the currently running Javascript finishes, the next event is pulled from the event queue and it gets its turn to run.
So, when you do an infinite loop such as while(flag==false) {}, the currently running Javascript never finishes and thus the next event is never pulled from the event queue and thus the value of flag never gets changed. They key here is that Javascript is not interrupt driven. When a timer fires, it does not interrupt the currently running Javascript, run some other Javascript and then let the currently running Javascript continue. It just gets put in the event queue waiting until the currently running Javascript is done to get its turn to run.
What you need to do is rethink how your code works and find a different way to trigger whatever code you want to run when the flag value changes. Javascript is designed as an event-driven language. So, what you need to do is figure out what events you can register an interest in so you can either listen for the event that might cause the flag to change and you can examine the flag on that event or you can trigger your own event from whatever code might change the flag or you can implement a callback function that whatever code changes that flag can call your callback whenever the piece of code responsible for changing the flag value would change it's value to true, it just calls the callback function and thus your code that wants to run when the flag gets set to true will get to run at the right time. This is much, much more efficient than trying to use some sort of timer to constantly check the flag value.
function codeThatMightChangeFlag(callback) {
// do a bunch of stuff
if (condition happens to change flag value) {
// call the callback to notify other code
callback();
}
}
Solution using Promise, async\await and EventEmitter which allows to react immediate on flag change without any kind of loops at all
const EventEmitter = require('events');
const bus = new EventEmitter();
let lock = false;
async function lockable() {
if (lock) await new Promise(resolve => bus.once('unlocked', resolve));
....
lock = true;
...some logic....
lock = false;
bus.emit('unlocked');
}
EventEmitter is builtin in node. In browser you shall need to include it by your own, for example using this package: https://www.npmjs.com/package/eventemitter3
ES6 with Async / Await ,
let meaningOfLife = false;
async function waitForMeaningOfLife(){
while (true){
if (meaningOfLife) { console.log(42); return };
await null; // prevents app from hanging
}
}
waitForMeaningOfLife();
setTimeout(()=>meaningOfLife=true,420)
Modern solution using Promise
myFunction() in the original question can be modified as follows
async function myFunction(number) {
var x=number;
...
... more initializations
await until(_ => flag == true);
...
... do something
}
where until() is this utility function
function until(conditionFunction) {
const poll = resolve => {
if(conditionFunction()) resolve();
else setTimeout(_ => poll(resolve), 400);
}
return new Promise(poll);
}
Some references to async/await and arrow functions are in a similar post:
https://stackoverflow.com/a/52652681/209794
function waitFor(condition, callback) {
if(!condition()) {
console.log('waiting');
window.setTimeout(waitFor.bind(null, condition, callback), 100); /* this checks the flag every 100 milliseconds*/
} else {
console.log('done');
callback();
}
}
Use:
waitFor(() => window.waitForMe, () => console.log('got you'))
I solved this issue by implementing the method below.
const waitUntil = (condition, checkInterval=100) => {
return new Promise(resolve => {
let interval = setInterval(() => {
if (!condition()) return;
clearInterval(interval);
resolve();
}, checkInterval)
})
}
Now, whenever you want to wait until a certain condition is met you can call it like this.
await waitUntil(() => /* your condition */)
const waitUntil = (condition, checkInterval=100) => {
return new Promise(resolve => {
let interval = setInterval(() => {
if (!condition()) return;
clearInterval(interval);
resolve();
}, checkInterval)
})
}
async function start() {
let flag = false;
console.log('wait 5 sec');
setTimeout(()=> {flag=true}, 5000); // set flag=true after 5 seconds
await waitUntil(() => flag==true ); // wait
console.log('do something when flag is true...');
}
start();
With Ecma Script 2017 You can use async-await and while together to do that
And while will not crash or lock the program even variable never be true
//First define some delay function which is called from async function
function __delay__(timer) {
return new Promise(resolve => {
timer = timer || 2000;
setTimeout(function () {
resolve();
}, timer);
});
};
//Then Declare Some Variable Global or In Scope
//Depends on you
var flag = false;
//And define what ever you want with async fuction
async function some() {
while (!flag)
await __delay__(1000);
//...code here because when Variable = true this function will
};
For iterating over ($.each) objects and executing a longish-running operation (containing nested ajax sync calls) on each object:
I first set a custom done=false property on each.
Then, in a recursive function, set each done=true and continued using setTimeout. (It's an operation meant to stop all other UI, show a progress bar and block all other use so I forgave myself for the sync calls.)
function start()
{
GlobalProducts = getproductsfromsomewhere();
$.each(GlobalProducts, function(index, product) {
product["done"] = false;
});
DoProducts();
}
function DoProducts()
{
var doneProducts = Enumerable.From(GlobalProducts).Where("$.done == true").ToArray(); //linqjs
//update progress bar here
var nextProduct = Enumerable.From(GlobalProducts).Where("$.done == false").First();
if (nextProduct) {
nextProduct.done = true;
Me.UploadProduct(nextProduct.id); //does the long-running work
setTimeout(Me.UpdateProducts, 500)
}
}
If you are allowed to use: async/await on your code, you can try this one:
const waitFor = async (condFunc: () => boolean) => {
return new Promise((resolve) => {
if (condFunc()) {
resolve();
}
else {
setTimeout(async () => {
await waitFor(condFunc);
resolve();
}, 100);
}
});
};
const myFunc = async () => {
await waitFor(() => (window as any).goahead === true);
console.log('hello world');
};
myFunc();
Demo here:
https://stackblitz.com/edit/typescript-bgtnhj?file=index.ts
On the console, just copy/paste: goahead = true.
TMCDR; (= "too much code... didn't read")
Simplest in terms of readability of the calling code and conciseness of the implementing code:
const until = (predFn) => {
const poll = (done) => (predFn() ? done() : setTimeout(() => poll(done), 500));
return new Promise(poll);
};
Example calling code:
await until(() => { myBankBalance > 1000000 });
More detailed example:
https://replit.com/#SteveChambers1/Javascript-until-function?v=1
The cleanest solution (improvement of #tdxius solution) based on controlled time interval loop, promise and timeout to reject the promise and clear intervals in case condition isn't met in a given time
const waitUntil = (condition) => {
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
if (!condition()) {
return;
}
clearInterval(interval);
resolve();
}, 100);
setTimeout(() => {
clearInterval(interval);
reject('your error msg');
}, 5000);
});
};
Now, whenever you want to wait until a certain condition is met, you can call it like this.
waitUntil(CONDITION_FUNCTION)
.then(() => DO_SOMETHING)
.catch((YOUR_ERROR_MSG) => console.warn(YOUR_ERROR_MSG))
using non blocking javascript with EventTarget API
In my example, i need to wait for a callback before to use it. I have no idea when this callback is set. It can be before of after i need to execute it. And i can need to call it several time (everything async)
// bus to pass event
const bus = new EventTarget();
// it's magic
const waitForCallback = new Promise((resolve, reject) => {
bus.addEventListener("initialized", (event) => {
resolve(event.detail);
});
});
// LET'S TEST IT !
// launch before callback has been set
waitForCallback.then((callback) => {
console.log(callback("world"));
});
// async init
setTimeout(() => {
const callback = (param) => { return `hello ${param.toString()}`; }
bus.dispatchEvent(new CustomEvent("initialized", {detail: callback}));
}, 500);
// launch after callback has been set
setTimeout(() => {
waitForCallback.then((callback) => {
console.log(callback("my little pony"));
});
}, 1000);
Did anyone ever think of just doing this?
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 2000);
});
}
function myFunction(number) {
var x=number;
...
... more initializations
//here need to wait until flag==true
while(flag==false)
{
await resolveAfter2Seconds();
}
...
... do something
}
I tried to used #Kiran approach like follow:
checkFlag: function() {
var currentObject = this;
if(flag == false) {
setTimeout(currentObject.checkFlag, 100);
} else {
/* do something*/
}
}
(framework that I am using force me to define functions this way).
But without success because when execution come inside checkFlag function second time, this is not my object it is Window.
So, I finished with code below
checkFlag: function() {
var worker = setInterval (function(){
if(flag == true){
/* do something*/
clearInterval (worker);
}
},100);
}
there is a node package delay very easy to use
const delay = require('delay');
(async () => {
bar();
await delay(100);
// Executed 100 milliseconds later
baz();
})();
I took an approach along the lines of the callback solutions here, but tried to make it a bit more generic. The idea is you add functions that you need to execute after something changes to a queue. When the thing happens, you then loop through the queue, call the functions and empty the queue.
Add function to queue:
let _queue = [];
const _addToQueue = (funcToQ) => {
_queue.push(funcToQ);
}
Execute and flush the queue:
const _runQueue = () => {
if (!_queue || !_queue.length) {
return;
}
_queue.forEach(queuedFunc => {
queuedFunc();
});
_queue = [];
}
And when you invoke _addToQueue you'll want to wrap the callback:
_addToQueue(() => methodYouWantToCallLater(<pass any args here like you normally would>));
When you've met the condition, call _runQueue()
This was useful for me because I had several things that needed to wait on the same condition. And it decouples the detection of the condition from whatever needs to be executed when that condition is hit.
Try avoid while loop as it could be blocking your code, use async and promises.
Just wrote this library:
https://www.npmjs.com/package/utilzed
There is a function waitForTrue
import utilzed from 'utilzed'
const checkCondition = async () => {
// anything that you are polling for to be expecting to be true
const response = await callSomeExternalApi();
return response.success;
}
// this will waitForTrue checkCondition to be true
// checkCondition will be called every 100ms
const success = await utilzed.waitForTrue(100, checkCondition, 1000);
if (success) {
// Meaning checkCondition function returns true before 1000 ms
return;
}
// meaning after 1000ms the checkCondition returns false still
// handle unsuccessful "poll for true"
Similar to Lightbeard's answer, I use the following approach
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function until(fn) {
while (!fn()) {
await sleep(0)
}
}
async function myFunction(number) {
let x = number
...
... more initialization
await until(() => flag == true)
...
... do something
}
//function a(callback){
setTimeout(function() {
console.log('Hi I am order 1');
}, 3000);
// callback();
//}
//function b(callback){
setTimeout(function() {
console.log('Hi I am order 2');
}, 2000);
// callback();
//}
//function c(callback){
setTimeout(function() {
console.log('Hi I am order 3');
}, 1000);
// callback();
//}
/*function d(callback){
a(function(){
b(function(){
c(callback);
});
});
}
d();*/
async function funa(){
var pr1=new Promise((res,rej)=>{
setTimeout(()=>res("Hi4 I am order 1"),3000)
})
var pr2=new Promise((res,rej)=>{
setTimeout(()=>res("Hi4 I am order 2"),2000)
})
var pr3=new Promise((res,rej)=>{
setTimeout(()=>res("Hi4 I am order 3"),1000)
})
var res1 = await pr1;
var res2 = await pr2;
var res3 = await pr3;
console.log(res1,res2,res3);
console.log(res1);
console.log(res2);
console.log(res3);
}
funa();
async function f1(){
await new Promise(r=>setTimeout(r,3000))
.then(()=>console.log('Hi3 I am order 1'))
return 1;
}
async function f2(){
await new Promise(r=>setTimeout(r,2000))
.then(()=>console.log('Hi3 I am order 2'))
return 2;
}
async function f3(){
await new Promise(r=>setTimeout(r,1000))
.then(()=>console.log('Hi3 I am order 3'))
return 3;
}
async function finaloutput2(arr){
return await Promise.all([f3(),f2(),f1()]);
}
//f1().then(f2().then(f3()));
//f3().then(f2().then(f1()));
//finaloutput2();
//var pr1=new Promise(f3)
async function f(){
console.log("makesure");
var pr=new Promise((res,rej)=>{
setTimeout(function() {
console.log('Hi2 I am order 1');
}, 3000);
});
var result=await pr;
console.log(result);
}
// f();
async function g(){
console.log("makesure");
var pr=new Promise((res,rej)=>{
setTimeout(function() {
console.log('Hi2 I am order 2');
}, 2000);
});
var result=await pr;
console.log(result);
}
// g();
async function h(){
console.log("makesure");
var pr=new Promise((res,rej)=>{
setTimeout(function() {
console.log('Hi2 I am order 3');
}, 1000);
});
var result=await pr;
console.log(result);
}
async function finaloutput(arr){
return await Promise.all([f(),g(),h()]);
}
//finaloutput();
//h();
In my example, I log a new counter value every second:
var promises_arr = [];
var new_cntr_val = 0;
// fill array with promises
for (let seconds = 1; seconds < 10; seconds++) {
new_cntr_val = new_cntr_val + 5; // count to 50
promises_arr.push(new Promise(function (resolve, reject) {
// create two timeouts: one to work and one to resolve the promise
setTimeout(function(cntr) {
console.log(cntr);
}, seconds * 1000, new_cntr_val); // feed setTimeout the counter parameter
setTimeout(resolve, seconds * 1000);
}));
}
// wait for promises to finish
Promise.all(promises_arr).then(function (values) {
console.log("all promises have returned");
});
Modern and simple solution
async function waitUntil(condition, time = 100) {
while (!condition()) {
await new Promise((resolve) => setTimeout(resolve, time));
}
}
Usage
async function foo() {
await waitUntil(() => flag === true);
console.log('condition is met!');
}
Inspired by jfriend00, this worked for me
const seconds = new Date();
// wait 5 seconds for flag to become true
const waitTime = 5
const extraSeconds = seconds.setSeconds(seconds.getSeconds() + waitTime);
while (Date.now() < extraSeconds) {
// break when flag is false
if (flag === false) break;
}
I have a function I want to execute in the page using chrome.tabs.executeScript, running from a browser action popup. The permissions are set up correctly and it works fine with a synchronous callback:
chrome.tabs.executeScript(
tab.id,
{ code: `(function() {
// Do lots of things
return true;
})()` },
r => console.log(r[0])); // Logs true
The problem is that the function I want to call goes through several callbacks, so I want to use async and await:
chrome.tabs.executeScript(
tab.id,
{ code: `(async function() {
// Do lots of things with await
return true;
})()` },
async r => {
console.log(r); // Logs array with single value [Object]
console.log(await r[0]); // Logs empty Object {}
});
The problem is that the callback result r. It should be an array of script results, so I expect r[0] to be a promise that resolves when the script finishes.
Promise syntax (using .then()) doesn't work either.
If I execute the exact same function in the page it returns a promise as expected and can be awaited.
Any idea what I'm doing wrong and is there any way around it?
The problem is that events and native objects are not directly available between the page and the extension. Essentially you get a serialised copy, something like you will if you do JSON.parse(JSON.stringify(obj)).
This means some native objects (for instance new Error or new Promise) will be emptied (become {}), events are lost and no implementation of promise can work across the boundary.
The solution is to use chrome.runtime.sendMessage to return the message in the script, and chrome.runtime.onMessage.addListener in popup.js to listen for it:
chrome.tabs.executeScript(
tab.id,
{ code: `(async function() {
// Do lots of things with await
let result = true;
chrome.runtime.sendMessage(result, function (response) {
console.log(response); // Logs 'true'
});
})()` },
async emptyPromise => {
// Create a promise that resolves when chrome.runtime.onMessage fires
const message = new Promise(resolve => {
const listener = request => {
chrome.runtime.onMessage.removeListener(listener);
resolve(request);
};
chrome.runtime.onMessage.addListener(listener);
});
const result = await message;
console.log(result); // Logs true
});
I've extended this into a function chrome.tabs.executeAsyncFunction (as part of chrome-extension-async, which 'promisifies' the whole API):
function setupDetails(action, id) {
// Wrap the async function in an await and a runtime.sendMessage with the result
// This should always call runtime.sendMessage, even if an error is thrown
const wrapAsyncSendMessage = action =>
`(async function () {
const result = { asyncFuncID: '${id}' };
try {
result.content = await (${action})();
}
catch(x) {
// Make an explicit copy of the Error properties
result.error = {
message: x.message,
arguments: x.arguments,
type: x.type,
name: x.name,
stack: x.stack
};
}
finally {
// Always call sendMessage, as without it this might loop forever
chrome.runtime.sendMessage(result);
}
})()`;
// Apply this wrapper to the code passed
let execArgs = {};
if (typeof action === 'function' || typeof action === 'string')
// Passed a function or string, wrap it directly
execArgs.code = wrapAsyncSendMessage(action);
else if (action.code) {
// Passed details object https://developer.chrome.com/extensions/tabs#method-executeScript
execArgs = action;
execArgs.code = wrapAsyncSendMessage(action.code);
}
else if (action.file)
throw new Error(`Cannot execute ${action.file}. File based execute scripts are not supported.`);
else
throw new Error(`Cannot execute ${JSON.stringify(action)}, it must be a function, string, or have a code property.`);
return execArgs;
}
function promisifyRuntimeMessage(id) {
// We don't have a reject because the finally in the script wrapper should ensure this always gets called.
return new Promise(resolve => {
const listener = request => {
// Check that the message sent is intended for this listener
if (request && request.asyncFuncID === id) {
// Remove this listener
chrome.runtime.onMessage.removeListener(listener);
resolve(request);
}
// Return false as we don't want to keep this channel open https://developer.chrome.com/extensions/runtime#event-onMessage
return false;
};
chrome.runtime.onMessage.addListener(listener);
});
}
chrome.tabs.executeAsyncFunction = async function (tab, action) {
// Generate a random 4-char key to avoid clashes if called multiple times
const id = Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
const details = setupDetails(action, id);
const message = promisifyRuntimeMessage(id);
// This will return a serialised promise, which will be broken
await chrome.tabs.executeScript(tab, details);
// Wait until we have the result message
const { content, error } = await message;
if (error)
throw new Error(`Error thrown in execution script: ${error.message}.
Stack: ${error.stack}`)
return content;
}
This executeAsyncFunction can then be called like this:
const result = await chrome.tabs.executeAsyncFunction(
tab.id,
// Async function to execute in the page
async function() {
// Do lots of things with await
return true;
});
This wraps the chrome.tabs.executeScript and chrome.runtime.onMessage.addListener, and wraps the script in a try-finally before calling chrome.runtime.sendMessage to resolve the promise.
Passing promises from page to content script doesn't work, the solution is to use chrome.runtime.sendMessage and to send only simple data between two worlds eg.:
function doSomethingOnPage(data) {
fetch(data.url).then(...).then(result => chrome.runtime.sendMessage(result));
}
let data = JSON.stringify(someHash);
chrome.tabs.executeScript(tab.id, { code: `(${doSomethingOnPage})(${data})` }, () => {
new Promise(resolve => {
chrome.runtime.onMessage.addListener(function listener(result) {
chrome.runtime.onMessage.removeListener(listener);
resolve(result);
});
}).then(result => {
// we have received result here.
// note: async/await are possible but not mandatory for this to work
logger.error(result);
}
});
For anyone who is reading this but using the new manifest version 3 (MV3), note that this should now be supported.
chrome.tabs.executeScript has been replaced by chrome.scripting.executeScript, and the docs explicitly state that "If the [injected] script evaluates to a promise, the browser will wait for the promise to settle and return the resulting value."
I have javascript function like this:
function myFunction(number) {
var x=number;
...
... more initializations
//here need to wait until flag==true
while(flag==false)
{}
...
... do something
}
The problem is that the javascript is stuck in the while and stuck my program. so my question is how can I wait in the middle of the function until flag is true without "busy-wait"?
Javascript is single threaded, hence the page blocking behaviour. You can use the deferred/promise approach suggested by others. The most basic way would be to use window.setTimeout. E.g.
function checkFlag() {
if(flag === false) {
window.setTimeout(checkFlag, 100); /* this checks the flag every 100 milliseconds*/
} else {
/* do something*/
}
}
checkFlag();
Here is a good tutorial with further explanation: Tutorial
EDIT
As others pointed out, the best way would be to re-structure your code to use callbacks. However, this answer should give you an idea how you can 'simulate' an asynchronous behaviour with window.setTimeout.
You created an infinite loop where the flag value that terminates the loop can never be changed by code outside this loop because no code outside the loop ever gets to run - thus this will never work.
Because javascript in a browser is single threaded (except for webworkers which aren't involved here) and one thread of javascript execution runs to completion before another can run, your statement:
// this won't work!
while(flag==false) {}
will simply run forever (or until the browser complains about a non-responsive javascript loop), the page will appear to be hung and no other javascript will ever get a chance to run, thus the flag's value can never be changed by code outside this loop.
For a little more explanation, Javascript is an event driven language. That means that it runs a piece of Javascript until it returns control back to the interpreter. Then, only when it returns back to the interpreter, Javascript gets the next event from the event queue and runs it.
All things like timers and network events run through the event queue. So, when a timer fires or a network request arrives, it does not ever "interrupt" the currently running Javascript. Instead, an event gets put in the Javascript event queue and then, when the currently running Javascript finishes, the next event is pulled from the event queue and it gets its turn to run.
So, when you do an infinite loop such as while(flag==false) {}, the currently running Javascript never finishes and thus the next event is never pulled from the event queue and thus the value of flag never gets changed. They key here is that Javascript is not interrupt driven. When a timer fires, it does not interrupt the currently running Javascript, run some other Javascript and then let the currently running Javascript continue. It just gets put in the event queue waiting until the currently running Javascript is done to get its turn to run.
What you need to do is rethink how your code works and find a different way to trigger whatever code you want to run when the flag value changes. Javascript is designed as an event-driven language. So, what you need to do is figure out what events you can register an interest in so you can either listen for the event that might cause the flag to change and you can examine the flag on that event or you can trigger your own event from whatever code might change the flag or you can implement a callback function that whatever code changes that flag can call your callback whenever the piece of code responsible for changing the flag value would change it's value to true, it just calls the callback function and thus your code that wants to run when the flag gets set to true will get to run at the right time. This is much, much more efficient than trying to use some sort of timer to constantly check the flag value.
function codeThatMightChangeFlag(callback) {
// do a bunch of stuff
if (condition happens to change flag value) {
// call the callback to notify other code
callback();
}
}
Solution using Promise, async\await and EventEmitter which allows to react immediate on flag change without any kind of loops at all
const EventEmitter = require('events');
const bus = new EventEmitter();
let lock = false;
async function lockable() {
if (lock) await new Promise(resolve => bus.once('unlocked', resolve));
....
lock = true;
...some logic....
lock = false;
bus.emit('unlocked');
}
EventEmitter is builtin in node. In browser you shall need to include it by your own, for example using this package: https://www.npmjs.com/package/eventemitter3
ES6 with Async / Await ,
let meaningOfLife = false;
async function waitForMeaningOfLife(){
while (true){
if (meaningOfLife) { console.log(42); return };
await null; // prevents app from hanging
}
}
waitForMeaningOfLife();
setTimeout(()=>meaningOfLife=true,420)
Modern solution using Promise
myFunction() in the original question can be modified as follows
async function myFunction(number) {
var x=number;
...
... more initializations
await until(_ => flag == true);
...
... do something
}
where until() is this utility function
function until(conditionFunction) {
const poll = resolve => {
if(conditionFunction()) resolve();
else setTimeout(_ => poll(resolve), 400);
}
return new Promise(poll);
}
Some references to async/await and arrow functions are in a similar post:
https://stackoverflow.com/a/52652681/209794
function waitFor(condition, callback) {
if(!condition()) {
console.log('waiting');
window.setTimeout(waitFor.bind(null, condition, callback), 100); /* this checks the flag every 100 milliseconds*/
} else {
console.log('done');
callback();
}
}
Use:
waitFor(() => window.waitForMe, () => console.log('got you'))
I solved this issue by implementing the method below.
const waitUntil = (condition, checkInterval=100) => {
return new Promise(resolve => {
let interval = setInterval(() => {
if (!condition()) return;
clearInterval(interval);
resolve();
}, checkInterval)
})
}
Now, whenever you want to wait until a certain condition is met you can call it like this.
await waitUntil(() => /* your condition */)
const waitUntil = (condition, checkInterval=100) => {
return new Promise(resolve => {
let interval = setInterval(() => {
if (!condition()) return;
clearInterval(interval);
resolve();
}, checkInterval)
})
}
async function start() {
let flag = false;
console.log('wait 5 sec');
setTimeout(()=> {flag=true}, 5000); // set flag=true after 5 seconds
await waitUntil(() => flag==true ); // wait
console.log('do something when flag is true...');
}
start();
With Ecma Script 2017 You can use async-await and while together to do that
And while will not crash or lock the program even variable never be true
//First define some delay function which is called from async function
function __delay__(timer) {
return new Promise(resolve => {
timer = timer || 2000;
setTimeout(function () {
resolve();
}, timer);
});
};
//Then Declare Some Variable Global or In Scope
//Depends on you
var flag = false;
//And define what ever you want with async fuction
async function some() {
while (!flag)
await __delay__(1000);
//...code here because when Variable = true this function will
};
For iterating over ($.each) objects and executing a longish-running operation (containing nested ajax sync calls) on each object:
I first set a custom done=false property on each.
Then, in a recursive function, set each done=true and continued using setTimeout. (It's an operation meant to stop all other UI, show a progress bar and block all other use so I forgave myself for the sync calls.)
function start()
{
GlobalProducts = getproductsfromsomewhere();
$.each(GlobalProducts, function(index, product) {
product["done"] = false;
});
DoProducts();
}
function DoProducts()
{
var doneProducts = Enumerable.From(GlobalProducts).Where("$.done == true").ToArray(); //linqjs
//update progress bar here
var nextProduct = Enumerable.From(GlobalProducts).Where("$.done == false").First();
if (nextProduct) {
nextProduct.done = true;
Me.UploadProduct(nextProduct.id); //does the long-running work
setTimeout(Me.UpdateProducts, 500)
}
}
If you are allowed to use: async/await on your code, you can try this one:
const waitFor = async (condFunc: () => boolean) => {
return new Promise((resolve) => {
if (condFunc()) {
resolve();
}
else {
setTimeout(async () => {
await waitFor(condFunc);
resolve();
}, 100);
}
});
};
const myFunc = async () => {
await waitFor(() => (window as any).goahead === true);
console.log('hello world');
};
myFunc();
Demo here:
https://stackblitz.com/edit/typescript-bgtnhj?file=index.ts
On the console, just copy/paste: goahead = true.
TMCDR; (= "too much code... didn't read")
Simplest in terms of readability of the calling code and conciseness of the implementing code:
const until = (predFn) => {
const poll = (done) => (predFn() ? done() : setTimeout(() => poll(done), 500));
return new Promise(poll);
};
Example calling code:
await until(() => { myBankBalance > 1000000 });
More detailed example:
https://replit.com/#SteveChambers1/Javascript-until-function?v=1
The cleanest solution (improvement of #tdxius solution) based on controlled time interval loop, promise and timeout to reject the promise and clear intervals in case condition isn't met in a given time
const waitUntil = (condition) => {
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
if (!condition()) {
return;
}
clearInterval(interval);
resolve();
}, 100);
setTimeout(() => {
clearInterval(interval);
reject('your error msg');
}, 5000);
});
};
Now, whenever you want to wait until a certain condition is met, you can call it like this.
waitUntil(CONDITION_FUNCTION)
.then(() => DO_SOMETHING)
.catch((YOUR_ERROR_MSG) => console.warn(YOUR_ERROR_MSG))
using non blocking javascript with EventTarget API
In my example, i need to wait for a callback before to use it. I have no idea when this callback is set. It can be before of after i need to execute it. And i can need to call it several time (everything async)
// bus to pass event
const bus = new EventTarget();
// it's magic
const waitForCallback = new Promise((resolve, reject) => {
bus.addEventListener("initialized", (event) => {
resolve(event.detail);
});
});
// LET'S TEST IT !
// launch before callback has been set
waitForCallback.then((callback) => {
console.log(callback("world"));
});
// async init
setTimeout(() => {
const callback = (param) => { return `hello ${param.toString()}`; }
bus.dispatchEvent(new CustomEvent("initialized", {detail: callback}));
}, 500);
// launch after callback has been set
setTimeout(() => {
waitForCallback.then((callback) => {
console.log(callback("my little pony"));
});
}, 1000);
Did anyone ever think of just doing this?
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 2000);
});
}
function myFunction(number) {
var x=number;
...
... more initializations
//here need to wait until flag==true
while(flag==false)
{
await resolveAfter2Seconds();
}
...
... do something
}
I tried to used #Kiran approach like follow:
checkFlag: function() {
var currentObject = this;
if(flag == false) {
setTimeout(currentObject.checkFlag, 100);
} else {
/* do something*/
}
}
(framework that I am using force me to define functions this way).
But without success because when execution come inside checkFlag function second time, this is not my object it is Window.
So, I finished with code below
checkFlag: function() {
var worker = setInterval (function(){
if(flag == true){
/* do something*/
clearInterval (worker);
}
},100);
}
there is a node package delay very easy to use
const delay = require('delay');
(async () => {
bar();
await delay(100);
// Executed 100 milliseconds later
baz();
})();
I took an approach along the lines of the callback solutions here, but tried to make it a bit more generic. The idea is you add functions that you need to execute after something changes to a queue. When the thing happens, you then loop through the queue, call the functions and empty the queue.
Add function to queue:
let _queue = [];
const _addToQueue = (funcToQ) => {
_queue.push(funcToQ);
}
Execute and flush the queue:
const _runQueue = () => {
if (!_queue || !_queue.length) {
return;
}
_queue.forEach(queuedFunc => {
queuedFunc();
});
_queue = [];
}
And when you invoke _addToQueue you'll want to wrap the callback:
_addToQueue(() => methodYouWantToCallLater(<pass any args here like you normally would>));
When you've met the condition, call _runQueue()
This was useful for me because I had several things that needed to wait on the same condition. And it decouples the detection of the condition from whatever needs to be executed when that condition is hit.
Try avoid while loop as it could be blocking your code, use async and promises.
Just wrote this library:
https://www.npmjs.com/package/utilzed
There is a function waitForTrue
import utilzed from 'utilzed'
const checkCondition = async () => {
// anything that you are polling for to be expecting to be true
const response = await callSomeExternalApi();
return response.success;
}
// this will waitForTrue checkCondition to be true
// checkCondition will be called every 100ms
const success = await utilzed.waitForTrue(100, checkCondition, 1000);
if (success) {
// Meaning checkCondition function returns true before 1000 ms
return;
}
// meaning after 1000ms the checkCondition returns false still
// handle unsuccessful "poll for true"
Similar to Lightbeard's answer, I use the following approach
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function until(fn) {
while (!fn()) {
await sleep(0)
}
}
async function myFunction(number) {
let x = number
...
... more initialization
await until(() => flag == true)
...
... do something
}
//function a(callback){
setTimeout(function() {
console.log('Hi I am order 1');
}, 3000);
// callback();
//}
//function b(callback){
setTimeout(function() {
console.log('Hi I am order 2');
}, 2000);
// callback();
//}
//function c(callback){
setTimeout(function() {
console.log('Hi I am order 3');
}, 1000);
// callback();
//}
/*function d(callback){
a(function(){
b(function(){
c(callback);
});
});
}
d();*/
async function funa(){
var pr1=new Promise((res,rej)=>{
setTimeout(()=>res("Hi4 I am order 1"),3000)
})
var pr2=new Promise((res,rej)=>{
setTimeout(()=>res("Hi4 I am order 2"),2000)
})
var pr3=new Promise((res,rej)=>{
setTimeout(()=>res("Hi4 I am order 3"),1000)
})
var res1 = await pr1;
var res2 = await pr2;
var res3 = await pr3;
console.log(res1,res2,res3);
console.log(res1);
console.log(res2);
console.log(res3);
}
funa();
async function f1(){
await new Promise(r=>setTimeout(r,3000))
.then(()=>console.log('Hi3 I am order 1'))
return 1;
}
async function f2(){
await new Promise(r=>setTimeout(r,2000))
.then(()=>console.log('Hi3 I am order 2'))
return 2;
}
async function f3(){
await new Promise(r=>setTimeout(r,1000))
.then(()=>console.log('Hi3 I am order 3'))
return 3;
}
async function finaloutput2(arr){
return await Promise.all([f3(),f2(),f1()]);
}
//f1().then(f2().then(f3()));
//f3().then(f2().then(f1()));
//finaloutput2();
//var pr1=new Promise(f3)
async function f(){
console.log("makesure");
var pr=new Promise((res,rej)=>{
setTimeout(function() {
console.log('Hi2 I am order 1');
}, 3000);
});
var result=await pr;
console.log(result);
}
// f();
async function g(){
console.log("makesure");
var pr=new Promise((res,rej)=>{
setTimeout(function() {
console.log('Hi2 I am order 2');
}, 2000);
});
var result=await pr;
console.log(result);
}
// g();
async function h(){
console.log("makesure");
var pr=new Promise((res,rej)=>{
setTimeout(function() {
console.log('Hi2 I am order 3');
}, 1000);
});
var result=await pr;
console.log(result);
}
async function finaloutput(arr){
return await Promise.all([f(),g(),h()]);
}
//finaloutput();
//h();
In my example, I log a new counter value every second:
var promises_arr = [];
var new_cntr_val = 0;
// fill array with promises
for (let seconds = 1; seconds < 10; seconds++) {
new_cntr_val = new_cntr_val + 5; // count to 50
promises_arr.push(new Promise(function (resolve, reject) {
// create two timeouts: one to work and one to resolve the promise
setTimeout(function(cntr) {
console.log(cntr);
}, seconds * 1000, new_cntr_val); // feed setTimeout the counter parameter
setTimeout(resolve, seconds * 1000);
}));
}
// wait for promises to finish
Promise.all(promises_arr).then(function (values) {
console.log("all promises have returned");
});
Modern and simple solution
async function waitUntil(condition, time = 100) {
while (!condition()) {
await new Promise((resolve) => setTimeout(resolve, time));
}
}
Usage
async function foo() {
await waitUntil(() => flag === true);
console.log('condition is met!');
}
Inspired by jfriend00, this worked for me
const seconds = new Date();
// wait 5 seconds for flag to become true
const waitTime = 5
const extraSeconds = seconds.setSeconds(seconds.getSeconds() + waitTime);
while (Date.now() < extraSeconds) {
// break when flag is false
if (flag === false) break;
}