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.
Related
TL;DR
Can't get an async/await function to do what an async function achieves by returning a custom new Promise object.
I'm trying to build a function that takes a string, loops through the words inside the string, and set an interval that logs each word after the set interval you assigned. Once it's done logging, a callback will log the total amount of words after the function has finish logging each of them. Below, you have the main function.
async function textReader (text, callback, interval = 1000) {
return new Promise((resolve, reject) => {
let counter = 0
let textArray = text.trim().split(' ')
let idInterval = setInterval( () => {
if(counter == textArray.length) {
callback(textArray.length)
clearInterval(idInterval)
resolve();
} else {
console.log(textArray[counter++])
}
}, interval)
})
}
Then, the callback that logs the quantity of words displayed:
function funCallback (wordQuantity) {
console.log(`Process complete - The text contains ${wordQuantity} words`)
}
And finally, an async function that tests the main function. It simply runs 3 times the main function and log each of them, one after the other, as it is supposed to be. The idea is that each await blocks the process until there is a resolved value (which actually means: while it is logging in the terminal each word), and once that await is done, jump to the next textReader function and so on.
async function test () {
try {
let time = 500
await textReader('When I find myself in times of trouble ', funCallback, time)
await textReader('mother Mary comes to me ', funCallback, time)
await textReader('speaking words of wisdom Let it be', funCallback, time)
} catch (err) { console.log(err)}
}
My issue is that I want the textReader function to be able to achieve the same behavior without having to return a new Promise, but using await (because I guess that's what an async/await function should be helpful with, right? achieve the same as an ES6 Promise)
Please have in mind that the goals of the whole program are to:
Log the words in a specific interval
If test() holds more than one textReader(), they have to be blocking, meaning that one has to wait for the other to finish logging its words, otherwise the words from all the tested functions would overlap one over the other - which would be very confusing BTW -.
Count how many words were logged in each string.
I just don't see how can it be solved without having to return from textReader() a new Promise but using await, as it should be in a async/await function.
One of the attempts to solve it with async/await (see below) didn't work; it just runs the 3 textReader() from test() functions all at the same time, overlapping the logs.
async function textReader (text, callback, time = 1000) {
let counter = 0
let textArray = text.trim().split(' ')
let loggingWords = async () => {
let idInterval = setInterval( () => {
if(counter == textArray.length) {
callback(textArray.length)
clearInterval(idInterval)
} else {
console.log(textoArray[contador++])
}
}, time)
}
let waitForLogingWords = await loggingWords()
return waitForLogingWords
};
As indicated in the comments, there is no way you can fully avoid calls to new Promise(). Once that's established we might as well embrace it.
Here is a generic solution to the "I want to use promises to process a list of items in sequence, with a delay between each item" problem.
const sequence = (iterable, payload, delay=0) =>
iterable.reduce((p, item) =>
p.then(() =>
new Promise(r => setTimeout(r, delay)).then(() => payload(item))
), Promise.resolve()
);
It takes a list of items and creates a promise chain, each promise fulfilling delay milliseconds after the previous one did.
Based on this it's easy to implement your textReader - it's a sequence over words that logs each word:
var textReader = (text, interval=1000) =>
sequence(text.trim().split(' '), word => console.log(word), interval);
Your function that processes the song again only is a sequence over lines, so it's equally easy to implement. This time as an async function (but that's just details - sequence(...).then(...) would work the same):
async function test() {
try {
await sequence([
'When I find myself in times of trouble ',
'mother Mary comes to me ',
'speaking words of wisdom Let it be'
], line => textReader(line, 500));
console.log('-all done-');
} catch (err) {
console.log(err);
}
}
Once we run test(), we get a neatly staggered output of words, each 1/2 second after the other:
When
I
find
myself
in
times
of
trouble
mother
Mary
comes
to
me
speaking
words
of
wisdom
Let
it
be
-all done-
I have an API which is limited regarding how many requests per minute (50/minute) I can send to any endpoint provided by that API.
In the following code-section, I filter the objects orders with an URL as property, every object with an URL that provides data should be stored in successfullResponses in my app.component.ts.
Promise.all(
orders.map(order => this.api.getURL(order.resource_url).catch(() => null))
).then(responses => {
const successfulResponses = responses.filter(response => response != null)
for(let data of successfulResponses) {
// some other requests should be sent with data here
}
});
There are more than 50 orders to check, but I just can check maximum 50 orders at once, so I try to handle it in my service. I set the first date when the first request is sent. After that I compare the dates of the new request with the first one. If the difference is over 60, I set the current date to the new one and set maxReq again to 50. If it is under 60, I check if there are requests left, if yes I send the request and if not I just wait one minute :
sleep(ms){
return new Promise(resolve => setTimeout(resolve, ms));
}
async getURL(){
if(!this.date){
let date = new Date();
this.date = date;
}
if((new Date().getSeconds() - this.date.getSeconds() > 60 )){
this.maxReq = 50;
this.date = new Date();
return this.http.get(url, this.httpOptions).toPromise();
} else {
if(this.maxReq > 0){
this.maxReq -= 1;
return this.http.get(url, this.httpOptions).toPromise();
} else{
console.log("wait");
await this.sleep(60*1000);
this.maxReq = 50;
this.date = new Date();
return this.http.get(url, this.httpOptions).toPromise();
}
}
}
However the code in app.component.tsis not waiting for the function getURL() and executes further code with requests which leads to the problem that I send ´too many requests too quickly´.
What can I do about that?
I had a similar problem while trying to use promises with multiple async functions. It's an easy thing to forget, but in order to make them all wait, you have to use await on the root line that calls the function in question.
I'm not entirely certain, but my presumption is that your await this.sleep(60*1000); line is indeed waiting for a timeout to occur, but whilst it is doing this, the code that called getURL() is executing the rest of its lines, because it did not have an await (or equivalent, like .then) before getURL().
The way I discovered this in my case was by using a good debugging tool (I used Chrome DevTools's own debugging features). I advise you do the same, adding breakpoints everywhere, and see where your code is going with each line.
Here is a short, rough example to show what I mean:
// This code increments a number from 1 to 2 to 3 and returns it each time after a delay of 1 second.
async function loop() {
for (i = 1; i <= 3; i++) {
console.log('Input start');
/* The following waits for result of aSync before continuing.
Without 'await', it would execute the last line
of this function whilst aSync's own 'await'
waited for its result.
--- This is where I think your code goes wrong. --- */
await aSync(i);
console.log('Input end');
}
}
async function aSync(num) {
console.log('Return start');
/* The following waits for the 1-second delay before continuing.
Without 'await', it would return a pending promise immediately
each time. */
let result = await new Promise(
// I'm not using arrow functions to show what it's doing more clearly.
function(rs, rj) {
setTimeout(
function() {
/* For those who didn't know, the following passes the number
into the 'resolved' ('rs') parameter of the promise's executor
function. Without doing this, the promise would never be fulfilled. */
rs(num);
}, 1000
)
}
);
console.log(result);
console.log('Return end');
}
loop();
Let's say there are two async functions:
async function asyncCall() {
for(var i=0;i<50000;i++){
console.log('1');
}
}
async function asyncCall2() {
for(var i=0;i<5;i++){
console.log('2');
}
}
And I call both functions:
asyncCall();
asyncCall2();
This is the output:
1 50.000 times, then 2 one time.
Shouldn't the 2 be logged somewhere between 50.000 and 1 time?
There is no threading in JavaScript. Since all your code is just executed in the one single thread of js, the output is expected.
Your misconception about async is quite common. You think that async implies some kind of "background work" but it is not! There is no threading involved in async! Within an async function you can just say "I did what I want to do, but know I need to wait.. maybe later I can carry on.. you can get the thread to do other stuff". But if other stuff started, than that other stuff is executed until it is done (or yields back the thread again).
To get your expected result you would need to yield back execution to the rest of the code from within the function - but still it is no threading and hence, if our awaited result arrived it is not processed until the currently running code is done.
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds(); // we will wait and during that time the single thread executing our code can do other stuff
console.log(result);
// expected output: 'resolved'
}
function fnCall2() {
console.log('fn2');
for(var i=0;i<500;i++) { // this will not be interrupted!
console.log(i);
}
}
asyncCall();
fnCall2();
The result of asyncCall will arrive after 2 seconds, but it does not mean it is processed immediately. If fn2 needs more time than the processing of the result is delayed. Try setting fn2 to iterate to 50000 - the resolve message will always be printed afterwards.
My code in order to create multiple placements in an online service that has a 60 write constraint per minute:
placementsToAdd.forEach((placement, index) => {
setTimeout(() => {
options.url = `https://api.company.com/placement?publisher_id=${existingPub ? existingPub : placementsJson[0].PublisherId}&site_id=${placement.siteId}`
options.body = `{"placement":{"name":"${placement.placement}"}}`
request(options, callback);
},1000 * (index + 1))
})
It works this way but I am concerned about the wait time if there are a list of placements of 2000 or 3000 at one time, the wait time might be excessively long.
Is there a better way to refactor this code in order to get my requests built one per second no matter what? Without that "* (index + 1)," it seems to keep trying to build all at once hitting the wall after 60.
I've tried to use promises and async await (which is new to me) but it doesn't seem to change the behavior.
Thanks!
As requested, show how I've tried to use promises with this code:
async function createThePlacements() {
let promise = new Promise((resolve, reject) => {
for (let i = 0; i < placementsToAdd.length; i++) {
setTimeout(() => {
options.url = `https://api.company.com/placement?publisher_id=${existingPub ? existingPub : placementsJson[0].PublisherId}&site_id=${placementsToAdd[i].siteId}`
options.body = `{"placement":{"name":"${placementsToAdd[i].placement}"}}`
request(options, callback);
},1000)
}
});
let result = await promise; // pause till the promise resolves
console.log('result - ', result);
}
createThePlacements();
So, bit of a disclaimer - as mentioned, I've never used Async Await before so reading up to try to understand how it works as well. This seems to be the syntax but my result doesn't seem to be anything at the moment but the code also continues to do what it's supposed to do, just trying to make all the calls in my test of 300 all at once.
Also, of note, i have a resolve inside the callback of the request call. It resolves so even the next parts of my app finish up all the way to the end. That's why I don't have a reject or resolve here.
How do you run a setTimeout once a second inside a forEach loop?
The most straightforward approach would be:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
for (const placement of placementsToAdd) {
const options = {...};
request(options, callback);
await wait(1000);
}
await works predictably inside plain for-loops, not inside forEach.
I haven't touched your callback but it would need to handle errors. More refactoring is possible.
The most significant improvement here, I think, is that we're not pushing requests ahead of time. This way we retain control, and should needs change or anything go haywire, we can break out of the loop without spamming the server for another minute.
The best option would be to have a request method that returns a Promise.
Then you could rewrite your code like this.
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function requestPlacement(placement) {
const options = {...};
return request(options);
}
async function requestAllPlacements(placements) {
for(let i = 0; i < placements.length; i+=60) {
if (i > 0) {
// wait 1 minute
await(sleep(60000));
}
await Promise.all(
placements
.slice(i, 60)
.map(requestPlacement);
);
}
}
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