I'm receiving data in browser through websockets (paho-mqtt) but problem is that the receiving callback gets fired only when another task ends (big for loop) and it gets fired with all the stacked data, I'm not losing data just getting delayed. Shouldn't the callback get fired even if there is a loop running? What is happening here?. Otherwise, how can I achieve this, keep receiving while inside a loop?
What I'm trying to say is equivalent to the following:
If I do this in chrome
setTimeout(() => {
console.log('hello!');
}, 10);
for (var i = 0; i < 50000; i++) {
console.log('for array');
}
I get
50000 VM15292:5 for array
VM15292:2 hello!
Shouldn't I get something like this?
1000 VM15292:5 for array
VM15292:2 hello!
49000 VM15292:5 for array
When you run JavaScript code in the browser (unless using Web Workers or other special technologies), it is executed on a single thread. That might not sound too important, but it is.
Your code consists of a for-loop (synchronous) and a call to setTimeout (asychronous). Since only one piece of JavaScript can be running at once, your for-loop will never be interrupted by setTimeout.
In fact, if your for-loop contained extremely intensive operations that required more than 10 ms to complete, your setTimeout callback might actually be delayed past that mark, because the browser always wait for the currently executing code to finish before continuing to run the event loop.
setTimeout(() => {
console.log('hello!');
}, 10);
for (var i = 0; i < /* 50000 */ 5; i++) {
console.log('for array');
}
The others have diagnosed the problem well, the single threaded nature of the browser. I will offer a possible solution: generators.
Here's a codepen which demonstrates the problem:
http://codepen.io/anon/pen/zZwXem?editors=1111
window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 60000;
function log(message) {
const output = document.getElementById('output');
output.value = output.value + '\n' + message;
}
function asyncTask() {
log('Simulated websocket message')
}
function doWork() {
const timer = setInterval(1000, asyncTask);
let total = 0;
for (let i = 1; i < 100000000; i++) {
const foo = Math.log(i) * Math.sin(i);
total += foo;
}
log('The total is: '+ total);
clearInterval(timer);
}
When doWork() is called by clicking the 'Do Work' button, the asyncTask never runs, and the UI locks up. Horrible UX.
The following example uses a generator to run the long running task.
http://codepen.io/anon/pen/jBmoPZ?editors=1111
//Basically disable codepen infinite loop detection, which is faulty for generators
window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 120000;
let workTimer;
function log(message) {
const output = document.getElementById('output');
output.value = output.value + '\n' + message;
}
function asyncTask() {
log('Simulated websocket message')
}
let workGenerator = null;
function runWork() {
if (workGenerator === null) {
workGenerator = doWork();
}
const work = workGenerator.next();
if (work.done) {
log('The total is: '+ work.value);
workerGenerator = null;
} else {
workTimer = setTimeout(runWork,0);
}
}
function* doWork() {
const timer = setInterval(asyncTask,1000);
let total = 0;
for (let i = 1; i < 100000000; i++) {
if (i % 100000 === 0) {
yield;
}
if (i % 1000000 == 0) {
log((i / 100000000 * 100).toFixed(1) + '% complete');
}
const foo = Math.log(i) * Math.sin(i);
total += foo;
}
clearInterval(timer);
return total;
}
Here we do work in a generator, and create a generator runner to call from the 'Do Work' button in the UI. This runs on the latest version of Chrome, I can't speak for other browsers. Typically you'd use something like babel to compile the generators down to ES5 syntax for a production build.
The generator yields every 10000 rows of calculation, and emits a status update every 100000 rows. The generator runner 'runWork' creates an instance of the generator and repeatedly calls next(). The generator then runs until it hits the next 'yield' or return statement. After the generator yields, the generator runner then gives up the UI thread by calling setTimeout with 0 milliseconds and using itself as the handler function. This typically means it will get called once every animation frame (ideally). This goes until the generator returns the done flag, at which point the generator runner can get the returned value and clean up.
Here the HTML for the example in case you need to recreate the codepen:
<input type='button' value='Do Work' onclick=doWork() />
<textarea id='output' style='width:200px;height:200px'></textarea>
Javascript engines tends to be single threaded.
So if you are in a long running tight loop that doesn't yield (e.g. to do some io) then the callback will never get a chance to run until the loop finishes
Related
I'm learning about async and I'm trying to experiment with setTimeout to print out a string with a 50ms delay then finally print a newline after the loop is done.
With my current code it prints the newline before the loop is done.
const str = "hello world";
let ms = 0;
for (let char of str) {
setTimeout(() => {
process.stdout.write(char);
}, ms);
ms += 50;
}
console.log('\n');
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms));
}
const printSomethingDelayed = myString => {
return sleep(200).then(v => console.log(myString));
}
const forLoop = async _ => {
console.log('Start');
const myString = "Hello world";
for (let index = 0; index < myString.length; index++) {
await printSomethingDelayed(myString[index]);
}
console.log('End');
console.log('\n');
}
forLoop();
Time is so small that you are not able to see the difference. Use 500 instead of 50.
Note: 1000 ms = 1 second.
You can use promise or async await for this. The example of using promise is here
const str = "hello world";
let ms = 0;
for (let char of str) {
change(char, ms);
ms += 50;
}
function change(char, ms) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
console.log(char);
resolve("done")
}, ms);
}).then(function() {
console.log("\n")
});
}
const str = "hello world\n";
let ms = 0;
for (let char of str) {
setTimeout(() => {
process.stdout.write(char);
}, ms);
ms += 50;
}
That's how asynchronous programming works :-)
In JavaScript, each block is pushed to stack synchronously and block gets executed. However, whenever stack recognizes any block as functions of WebAPI (in this case setTimeout), stack hands it over to the browser to handle the execution.
Once the task is finished by the browser, they are pushed back to stack (called as callback). When a stack is empty, these call back functions are executed by the stack.
In the given example, when stack sees setTimeout function, it hands over to browser for execution and move to next block which is console.log() and executes it. and now stack is empty and ready to execute callback functions.
If you want to print console log after setTimeout, you can be used promises or async-await.
If you want to learn more about asynchronous programming, do check out this video by Philip Roberts at JSConfEU
const str = "hello world";
let ms = 0;
const print = function(i, ms, str){
const c = str.charAt(i);
if(!c){
console.log('\n');
}
else{
setTimeout(function(){
console.log(c);
print(i+1, ms+50, str);
}, ms);
}
};
print(0, ms, str);
Even if you set every setTimeout to 0ms, the new line would still print first.
setTimeout (like all asynchronous functions before ES6) is not part of the JavaScript engine, so it doesn't go onto the regular call stack when the parser encounters it. Instead, setTimeout schedules browser API calls, and then parser moves on.
So when the parser encounters all the 'setTimeouts' in the for loop, the browser will make API calls and we are on our way to the next thing. The next thing happens to be a console.log() in your case. That goes onto the regular call stack, and executed.
In the meantime, the browser is making an API call, and the result of setTimeout gets put into something called the callback queue. Your function's returns will sit there, in line, in the expected order.
Now the call stack is clear. Here is the interesting part. Something called the Event Loop is constantly checking and checking and checking to see if the call stack is clear. If it is, it looks to the callback queue. The event loop then puts the function returns from the callback queue onto the call stack, and they are executed in order.
This is a lot of information and frankly I don't think any answer here will be good enough to solidify this understanding, so I highly recommend this youtube video on the topic: https://www.youtube.com/watch?v=8aGhZQkoFbQ. This video gives the proper mental model for this exact behavior.
I have WebWorker doing computationally intensive recursive calculation, lasting for several seconds. I would like to post message with progress to parent thread (main window) let say every 500 milliseconds.
I tried to use setInterval to achieve this. But since thread is blocked by main calculation, setInterval was not executed at all during that time.
Web worker code:
// global variable holding some partial information
let temporal = 0;
// time intensive recursive function. Fibonacci is chosen as an example here.
function fibonacci(num) {
// store current num into global variable
temporal = num;
return num <= 1
? 1
: fibonacci(num - 1) + fibonacci(num - 2);
};
self.onmessage = function(e) {
// start calculation
const result = fibonacci(e.data.value);
postMessage({result});
}
setInterval(function() {
// post temporal solution in interval.
// While the thread is blocked by recursive calculation, this is not executed
postMessage({progress: temporal});
}, 500);
Main window code
worker.onmessage = (e) => {
if (e.data.progress !== undefined) {
console.log('progress msg received')
} else {
console.log('result msg received')
console.log(e.data)
}
};
console.log('starting calculation');
worker.postMessage({
'value': 42,
});
See jsFiddle example - https://jsfiddle.net/m3geaxbo/36/
Of course, I could add some code to calculate passed time into fibonacci function and send message from there. But I don't like it, because it pollutes function with non-relevant code.
function fibonacci(num) {
// such approach will work, but it is not very nice.
if (passed500ms()) {
postMessage({progress: num})
}
return num <= 1
? 1
: fibonacci(num - 1) + fibonacci(num - 2);
};
Is there preferred way, how to get progress of the intensive web-worker calculation without polluting code performing calculation itself?
There is no way to let your algorithm perform synchronously without integrating some sort of yielding inside.
You'd have to adapt your algorithm so that you can pause it, and check if enough time has elapsed, or even let the event-loop to actually loop.
Letting the event loop perform other tasks is my personal favorite, since it also allows the main thread to communicate with the Worker, however, if you are really just wanting for it to verbose the current progress, a simple and synchronous time check is just fine.
Note that recursive functions by their very nature aren't really usable in such a case, because the values the function will generate at the 5th nesting level will not reflect the value you would have gotten by calling the main function with 5 as input.
So getting intermediate values using a recursive function is very tedious.
However, a fibonacci calculator can be rewritten inline really easily:
function fibonacci( n ) {
let a = 1, b = 0, temp;
while( n >= 0 ) {
temp = a;
a = a + b;
b = temp;
n--;
}
return b;
}
From here it's super easy to add the time-elapsed check and quite simple to rewrite it in a way we can pause it in the middle:
async function fibonacci( n ) {
let a = 1, b = 0, temp;
while( n >= 0 ) {
temp = a;
a = a + b;
b = temp;
n--;
if( n % batch_size === 0 ) { // we completed one batch
current_value = b; // let the outside scripts know where we are
await nextTask(); // let the event-loop loop.
}
}
return b;
}
To pause a function in the middle the async/await syntax comes very handy as it allows us to write a linear code, instead of having several intricated recursive callbacks.
The best thing you can use to let the event-loop to loop is, as demonstrated in this answer, to use a MessageChannel as a next-task scheduler.
Now, you can let your preferred scheduling method get in between these pauses and do the messaging to the main port, or listen for updates from the main thread.
But inlining your function also improves the performances so much that you can calculate the full sequence until Infinity in less than a few ms... (fibonacci( 1476 ) does return Infinity).
So fibonacci is not a great candidate to demonstrate this issue, let's rather calculate π.
I am borrowing a function to calculate PI from this answer, not judging if it's performant or not, it's simply for the sake of demonstrating how to let the Worker thread pause a long running function.
// Main thread code
const log = document.getElementById( "log" );
const url = generateWorkerURL();
const worker = new Worker( url );
worker.onmessage = ({data}) => {
const [ PI, iterations ] = data;
log.textContent = `π = ${ PI }
after ${ iterations } iterations.`
};
function generateWorkerURL() {
const script = document.querySelector( "[type='worker-script']" );
const blob = new Blob( [ script.textContent ], { type: "text/javascript" } );
return URL.createObjectURL( blob );
}
<script type="worker-script">
// The worker script
// Will get loaded dynamically in this snippet
// first some helper functions / monkey-patches
if( !self.requestAnimationFrame ) {
self.requestAnimationFrame = (cb) =>
setTimeout( cb, 16 );
}
function postTask( cb ) {
const channel = postTask.channel;
channel.port2.addEventListener( "message", () => cb(), { once: true } );
channel.port1.postMessage( "" );
}
(postTask.channel = new MessageChannel()).port2.start();
function nextTask() {
return new Promise( (res) => postTask( res ) );
}
// Now the actual code
// The actual processing
// borrowed from https://stackoverflow.com/a/50282537/3702797
// [addition]: made async so it can wait easily for next event loop
async function calculatePI( iterations = 10000 ) {
let pi = 0;
let iterator = sequence();
let i = 0;
// [addition]: start a new interval task
// which will report to main the current values
// using an rAF loop as it's the best to render on screen
requestAnimationFrame( function reportToMain() {
postMessage( [ pi, i ] );
requestAnimationFrame( reportToMain );
} );
// [addition]: define a batch_size
const batch_size = 10000;
for( ; i < iterations; i++ ){
pi += 4 / iterator.next().value;
pi -= 4 / iterator.next().value;
// [addition]: In case we completed one batch,
// we'll wait the next event loop iteration
// to let the interval callback fire.
if( i % batch_size === 0 ) {
await nextTask();
}
}
function* sequence() {
let i = 1;
while( true ){
yield i;
i += 2;
}
}
}
// Start the *big* job...
calculatePI( Infinity );
</script>
<pre id="log"></pre>
How can I make a simple, non-block Javascript function call? For example:
//begin the program
console.log('begin');
nonBlockingIncrement(10000000);
console.log('do more stuff');
//define the slow function; this would normally be a server call
function nonBlockingIncrement(n){
var i=0;
while(i<n){
i++;
}
console.log('0 incremented to '+i);
}
outputs
"beginPage"
"0 incremented to 10000000"
"do more stuff"
How can I form this simple loop to execute asynchronously and output the results via a callback function? The idea is to not block "do more stuff":
"beginPage"
"do more stuff"
"0 incremented to 10000000"
I've tried following tutorials on callbacks and continuations, but they all seem to rely on external libraries or functions. None of them answer the question in a vacuum: how does one write Javascript code to be non-blocking!?
I have searched very hard for this answer before asking; please don't assume I didn't look. Everything I found is Node.js specific ([1], [2], [3], [4], [5]) or otherwise specific to other functions or libraries ([6], [7], [8], [9], [10], [11]), notably JQuery and setTimeout(). Please help me write non-blocking code using Javascript, not Javascript-written tools like JQuery and Node. Kindly reread the question before marking it as duplicate.
To make your loop non-blocking, you must break it into sections and allow the JS event processing loop to consume user events before carrying on to the next section.
The easiest way to achieve this is to do a certain amount of work, and then use setTimeout(..., 0) to queue the next chunk of work. Crucially, that queueing allows the JS event loop to process any events that have been queued in the meantime before going on to the next piece of work:
function yieldingLoop(count, chunksize, callback, finished) {
var i = 0;
(function chunk() {
var end = Math.min(i + chunksize, count);
for ( ; i < end; ++i) {
callback.call(null, i);
}
if (i < count) {
setTimeout(chunk, 0);
} else {
finished.call(null);
}
})();
}
with usage:
yieldingLoop(1000000, 1000, function(i) {
// use i here
}, function() {
// loop done here
});
See http://jsfiddle.net/alnitak/x3bwjjo6/ for a demo where the callback function just sets a variable to the current iteration count, and a separate setTimeout based loop polls the current value of that variable and updates the page with its value.
SetTimeout with callbacks is the way to go. Though, understand your function scopes are not the same as in C# or another multi-threaded environment.
Javascript does not wait for your function's callback to finish.
If you say:
function doThisThing(theseArgs) {
setTimeout(function (theseArgs) { doThatOtherThing(theseArgs); }, 1000);
alert('hello world');
}
Your alert will fire before the function you passed will.
The difference being that alert blocked the thread, but your callback did not.
There are in general two ways to do this as far as I know. One is to use setTimeout (or requestAnimationFrame if you are doing this in a supporting environment). #Alnitak shown how to do this in another answer. Another way is to use a web worker to finish your blocking logic in a separate thread, so that the main UI thread is not blocked.
Using requestAnimationFrame or setTimeout:
//begin the program
console.log('begin');
nonBlockingIncrement(100, function (currentI, done) {
if (done) {
console.log('0 incremented to ' + currentI);
}
});
console.log('do more stuff');
//define the slow function; this would normally be a server call
function nonBlockingIncrement(n, callback){
var i = 0;
function loop () {
if (i < n) {
i++;
callback(i, false);
(window.requestAnimationFrame || window.setTimeout)(loop);
}
else {
callback(i, true);
}
}
loop();
}
Using web worker:
/***** Your worker.js *****/
this.addEventListener('message', function (e) {
var i = 0;
while (i < e.data.target) {
i++;
}
this.postMessage({
done: true,
currentI: i,
caller: e.data.caller
});
});
/***** Your main program *****/
//begin the program
console.log('begin');
nonBlockingIncrement(100, function (currentI, done) {
if (done) {
console.log('0 incremented to ' + currentI);
}
});
console.log('do more stuff');
// Create web worker and callback register
var worker = new Worker('./worker.js'),
callbacks = {};
worker.addEventListener('message', function (e) {
callbacks[e.data.caller](e.data.currentI, e.data.done);
});
//define the slow function; this would normally be a server call
function nonBlockingIncrement(n, callback){
const caller = 'nonBlockingIncrement';
callbacks[caller] = callback;
worker.postMessage({
target: n,
caller: caller
});
}
You cannot run the web worker solution as it requires a separate worker.js file to host worker logic.
You cannot execute Two loops at the same time, remember that JS is single thread.
So, doing this will never work
function loopTest() {
var test = 0
for (var i; i<=100000000000, i++) {
test +=1
}
return test
}
setTimeout(()=>{
//This will block everything, so the second won't start until this loop ends
console.log(loopTest())
}, 1)
setTimeout(()=>{
console.log(loopTest())
}, 1)
If you want to achieve multi thread you have to use Web Workers, but they have to have a separated js file and you only can pass objects to them.
But, I've managed to use Web Workers without separated files by genering Blob files and i can pass them callback functions too.
//A fileless Web Worker
class ChildProcess {
//#param {any} ags, Any kind of arguments that will be used in the callback, functions too
constructor(...ags) {
this.args = ags.map(a => (typeof a == 'function') ? {type:'fn', fn:a.toString()} : a)
}
//#param {function} cb, To be executed, the params must be the same number of passed in the constructor
async exec(cb) {
var wk_string = this.worker.toString();
wk_string = wk_string.substring(wk_string.indexOf('{') + 1, wk_string.lastIndexOf('}'));
var wk_link = window.URL.createObjectURL( new Blob([ wk_string ]) );
var wk = new Worker(wk_link);
wk.postMessage({ callback: cb.toString(), args: this.args });
var resultado = await new Promise((next, error) => {
wk.onmessage = e => (e.data && e.data.error) ? error(e.data.error) : next(e.data);
wk.onerror = e => error(e.message);
})
wk.terminate(); window.URL.revokeObjectURL(wk_link);
return resultado
}
worker() {
onmessage = async function (e) {
try {
var cb = new Function(`return ${e.data.callback}`)();
var args = e.data.args.map(p => (p.type == 'fn') ? new Function(`return ${p.fn}`)() : p);
try {
var result = await cb.apply(this, args); //If it is a promise or async function
return postMessage(result)
} catch (e) { throw new Error(`CallbackError: ${e}`) }
} catch (e) { postMessage({error: e.message}) }
}
}
}
setInterval(()=>{console.log('Not blocked code ' + Math.random())}, 1000)
console.log("starting blocking synchronous code in Worker")
console.time("\nblocked");
var proc = new ChildProcess(blockCpu, 43434234);
proc.exec(function(block, num) {
//This will block for 10 sec, but
block(10000) //This blockCpu function is defined below
return `\n\nbla bla ${num}\n` //Captured in the resolved promise
}).then(function (result){
console.timeEnd("\nblocked")
console.log("End of blocking code", result)
})
.catch(function(error) { console.log(error) })
//random blocking function
function blockCpu(ms) {
var now = new Date().getTime();
var result = 0
while(true) {
result += Math.random() * Math.random();
if (new Date().getTime() > now +ms)
return;
}
}
For very long tasks, a Web-Worker should be preferred, however for small-enough tasks (< a couple of seconds) or for when you can't move the task to a Worker (e.g because you needs to access the DOM or whatnot, Alnitak's solution of splitting the code in chunks is the way to go.
Nowadays, this can be rewritten in a cleaner way thanks to async/await syntax.
Also, instead of waiting for setTimeout() (which is delayed to at least 1ms in node-js and to 4ms everywhere after the 5th recursive call), it's better to use a MessageChannel.
So this gives us
const waitForNextTask = () => {
const { port1, port2 } = waitForNextTask.channel ??= new MessageChannel();
return new Promise( (res) => {
port1.addEventListener("message", () => res(), { once: true } );
port1.start();
port2.postMessage("");
} );
};
async function doSomethingSlow() {
const chunk_size = 10000;
// do something slow, like counting from 0 to Infinity
for (let i = 0; i < Infinity; i++ ) {
// we've done a full chunk, let the event-loop loop
if( i % chunk_size === 0 ) {
log.textContent = i; // just for demo, to check we're really doing something
await waitForNextTask();
}
}
console.log("Ah! Did it!");
}
console.log("starting my slow computation");
doSomethingSlow();
console.log("started my slow computation");
setTimeout(() => console.log("my slow computation is probably still running"), 5000);
<pre id="log"></pre>
Using ECMA async function it's very easy to write non-blocking async code, even if it performs CPU-bound operations. Let's do this on a typical academic task - Fibonacci calculation for the incredible huge value.
All you need is to insert an operation that allows the event loop to be reached from time to time. Using this approach, you will never freeze the user interface or I/O.
Basic implementation:
const fibAsync = async (n) => {
let lastTimeCalled = Date.now();
let a = 1n,
b = 1n,
sum,
i = n - 2;
while (i-- > 0) {
sum = a + b;
a = b;
b = sum;
if (Date.now() - lastTimeCalled > 15) { // Do we need to poll the eventloop?
lastTimeCalled = Date.now();
await new Promise((resolve) => setTimeout(resolve, 0)); // do that
}
}
return b;
};
And now we can use it (Live Demo):
let ticks = 0;
console.warn("Calulation started");
fibAsync(100000)
.then((v) => console.log(`Ticks: ${ticks}\nResult: ${v}`), console.warn)
.finally(() => {
clearTimeout(timer);
});
const timer = setInterval(
() => console.log("timer tick - eventloop is not freezed", ticks++),
0
);
As we can see, the timer is running normally, which indicates the event loop is not blocking.
I published an improved implementation of these helpers as antifreeze2 npm package. It uses setImmediate internally, so to get the maximum performance you need to import setImmediate polyfill for environments without native support.
Live Demo
import { antifreeze, isNeeded } from "antifreeze2";
const fibAsync = async (n) => {
let a = 1n,
b = 1n,
sum,
i = n - 2;
while (i-- > 0) {
sum = a + b;
a = b;
b = sum;
if (isNeeded()) {
await antifreeze();
}
}
return b;
};
If you are using jQuery, I created a deferred implementation of Alnitak's answer
function deferredEach (arr, batchSize) {
var deferred = $.Deferred();
var index = 0;
function chunk () {
var lastIndex = Math.min(index + batchSize, arr.length);
for(;index<lastIndex;index++){
deferred.notify(index, arr[index]);
}
if (index >= arr.length) {
deferred.resolve();
} else {
setTimeout(chunk, 0);
}
};
setTimeout(chunk, 0);
return deferred.promise();
}
Then you'll be able to use the returned promise to manage the progress and done callback:
var testArray =["Banana", "Orange", "Apple", "Mango"];
deferredEach(testArray, 2).progress(function(index, item){
alert(item);
}).done(function(){
alert("Done!");
})
I managed to get an extremely short algorithm using functions. Here is an example:
let l=($,a,f,r)=>{f(r||0),$((r=a(r||0))||0)&&l($,a,f,r)};
l
(i => i < 4, i => i+1, console.log)
/*
output:
0
1
2
3
*/
I know this looks very complicated, so let me explain what is really going on here.
Here is a slightly simplified version of the l function.
let l_smpl = (a,b,c,d) => {c(d||0);d=b(d||0),a(d||0)&&l_smpl(a,b,c,d)||0}
First step in the loop, l_smpl calls your callback and passes in d - the index. If d is undefined, as it would be on the first call, it changes it to 0.
Next, it updates d by calling your updater function and setting d to the result. In our case, the updater function would add 1 to the index.
The next step checks if your condition is met by calling the first function and checking if the value is true meaning the loop is not done. If so, it calls the function again, or otherwise, it returns 0 to end the loop.
I have a loop that cycles around and creates REST Queries, creating and running a different one each time.
In the future I imagine the number of REST Queries that could be made will increase far higher than the browser will take at once (over 20,000).
I want to do something along the lines of counting how many loops have been done, and after every 500 or so, pausing for a few seconds to allow the browser to catch up with the REST responses, and then continuing.
How is this done in react JS?
example code:
for (var i = 0; i < array.length; i++)
{
query += i;
axious.get(query) . then { ...does stuff here... }
//want something along the lines of if multiple of 500, wait(1000)
}
the simplest approach is to make a waitIf function that returns a Promise and accepts a condition.
If the condition is true, it will wait then execute the callback, otherwise, it will execute the callback directly.
a simple implementation would be.
function waitIf(condition, duration, callback) {
if (condition) {
// return a Promise that wait for a `duration` ms using timeout
return Promise((resolve) => {
setTimeout(() => {
resolve(callback());
}, duration);
})
}
return callback();
}
for (let i = 0; i < array.length; i++) {
query += i;
// unify the call here
waitIf(i % 500 === 0, 1000, () => axious.get(query)).then();
}
In my code, I'm trying to put a certain delay before continuing to the rest of the code. Pretty basic. I'm using a custom sleep function because javascript's native sleep function is not working at all. I'm actually working in app script in google spreadsheets so maybe that's why. But the following code is in the <script> tag of the html file in spreadsheet app script.
Anyway, when I use sleep(), it is being executed before the setTimeout
function get_ids(db){
window.alert("before window.ids is saved?");
google.script.run.withSuccessHandler(getIdsFromAppscript).getIdsFromModel(db);
//this returns a result to getIdsFromAppscript but the following code doesn't wait
//for the result to be returned so I want to use sleep before the ids
//variable is returned by get_ids
setTimeout(function(){
window.alert("checking if ids is saved after 10s?");
window.alert("In timeout ids="+window.ids);
var ids= window.ids; //is non empty
},10000);
sleep(10000);
var ids= window.ids;
window.alert("after wait");
window.alert("after sleep ids="+ids); //is empty but should be non empty
return ids; //=window.ids , Need to return a non-empty result
}
function getIdsFromAppscript(result){
window.ids=result;
}
and the sleep function:
function sleep(ms) {
var start = new Date().getTime(), expire = start + ms;
while (new Date().getTime() < expire) { }
return;
}
Current Order of output based on window.alert():
1) before window is saved?
2) after sleep
3) after sleep ids= //basically empty which shouldn't be the case
4) checking if ids is saved after 10s?
5) In timeout ids= [.....] //non empty list
However, my desired output order is:
1) before window is saved?
2) checking if ids is saved after 10s?
3) In timeout ids= [.....] //non empty list
4) after sleep
5) after sleep ids= [...] //should be a non empty list
The reason why I'm writing setTimeout is to show that after 10 seconds, the result is being stored in window.ids however even after I give a sleep for 10 seconds, or even 30 seconds, I can't get the result from window.ids.
What exactly am I doing wrong here? Thanks in advance~
Java Script, especially through the V8 engine does not sleep. Sleeping causes the entire thread that JavaScript runs on to stop, which breaks the whole point of asynchronocy. setTimeout() only waits to run the function you push into it for the time you also put into it. It doesn't stop the rest of the executions, and whatever happens first will happen first.
If it's important to you that something happens in order, always, then you need to use callbacks or promises.
An example in code could be:
function doTimeout(ms) {
setTimeout(function(){
window.alert("checking if ids is saved after 10s?");
window.alert("In timeout ids="+window.ids);
var ids= window.ids; //is non empty
},ms);
}
function sleep(ms, callback) {
var start = new Date().getTime(), expire = start + ms;
while (new Date().getTime() < expire) { }
callback(ms);
}
sleep(10000, doTimeout);
Javascript is single threaded. You must return from your code for scripts in other threads to execute. Script in other threads includes functions to handle a timeout event, functions called when promises are kept or fail, and call back functions provided for asynchronous requests made using an XMLHttpRequest object.
Writing a function and calling it sleep() does not change this. You could have called it waitingForGodot() for all the difference it would make. What the code you provided does is to spend a lot of time looping in the thread it was called in. It does not relinquish control and blocks all other scripts from executing. If it goes on for long enough my browser will ask me if I wish to abort the (as in your) script.
I have included two examples below showing that your sleep function blocks the entire Javascript engine. When I use your sleep function, the interval function does not get executed even though I have set an interval of 100 ms and the output is delayed by 10 seconds. However, in the second example the output does get printed immediately at the correct interval. This shows your sleep function is blocking the entire execution engine and that explains why your ids array is empty.
function sleep(ms) {
var start = new Date().getTime(),
expire = start + ms;
while (new Date().getTime() < expire) {}
return;
}
function get_ids() {
document.write("before window.ids is saved?" + "<br>");
var counter = 0;
setInterval(function() {
while (counter < 100) {
document.write("checking if ids is saved after 10s?" + "<br>");
counter = counter + 1
}
}, 100);
sleep(10000);
documen.write("after wait");
}
document.write("Start");
get_ids()
document.write("End");
In this example I have commented out your sleep function and as expected the output gets printed every 100 ms:
function sleep(ms) {
var start = new Date().getTime(),
expire = start + ms;
while (new Date().getTime() < expire) {}
return;
}
function get_ids() {
document.write("before window.ids is saved?" + "<br>");
var counter = 0;
setInterval(function() {
while (counter < 100) {
document.write("checking if ids is saved after 10s?" + "<br>");
counter = counter + 1
}
}, 100);
//sleep(10000);
documen.write("after wait");
}
document.write("Start");
get_ids()
document.write("End");