I've written a short script using Async/Await that prints out letters one by one after short intervals. Based on what I understood to be happening, I tried rewriting the code in several ways expecting the same result, but I have been unable to make any of these alternatives work. In particular, I thought it would be straightforward to change where in the code the console.log() happens.
Here's the original working code:
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// Promisify setTimeout() and feed in counter from sendMessage()
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
// Async/Await with a For loop calling setTimeoutPromise()
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
sendMessage();
}
welcomeMessage();
Then I tried to make a few modifications, none of which worked.
Mdofication #1: In this version, I thought I could just call and run the code in the sendMessage() function directly without needing to call it later. However, nothing happened after this modification:
async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// Promisify setTimeout() and feed in counter from sendMessage()
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
}
welcomeMessage();
Modification #2: I reverted the code and then tried to move the console.log() function into the setTimeout() function thinking this would be called on every loop. Both with empty ()'s and with (resolve) being passed into setTimeout(), it only printed the first letter. With (resolve, num) it says undefined:
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout((resolve) => {
console.log(message[num]);
resolve;
}, timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout((resolve) => {
console.log(message[num]);
resolve;
}, timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage();
}
welcomeMessage();
Modification #3: Finally, I tried to define a function in advance to be passed into setTimeout() which would be used to handle "resolve" and console.log(). I tried a few variations and again didn't seem to be progressing through the loop as console.log() was only called once.
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func;
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction(resolve, num), timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func;
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction(resolve, num), timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage()
}
welcomeMessage();
It appears to me, that you've started working with asynchrony before you got a deep understanding of how synchronous JavaScript works. Asynchrony is hard enough on its own too, so combined to that, it made you completely confused.
Let me explain what's going on and what's wrong in your snippets.
Let's start with the working one.
That code:
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
...creates a function named setTimeoutPromise, that:
takes an index (number) as its argument
returns a promise that:
after timer[num] milliseconds
resolves to undefined (setTimeout doesn't pass anything to its callback by default; in this case the callback is the resolve function)
The next part:
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
...defines an async function named sendMessage, that:
iterates over message, for each character:
calls setTimeoutPromise and awaits the promise it returns
after waiting, logs the current character to the console
Finally,
sendMessage();
...calls sendMessage, and therefore initiates the typing.
Now, let's move on to the next snippet.
This code:
async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
...creates an async function, but it doesn't call or assign it to any variable: simply discards it.
To fix this snippet, call the function immediately by putting () after it!
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// Promisify setTimeout() and feed in counter from sendMessage()
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
(async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
})(); //The () at the end calls it
}
welcomeMessage();
Problematic snippet #2
There are 2 problems with this:
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout((resolve) => { //Problem 1
console.log(message[num]);
resolve; //Problem 2
}, timer[num]);
})
};
You try to take an argument named resolve from setTimeout, but as I mentioned above, it doesn't pass any.
To solve it, remove resolve from setTimeout((resolve) => {! We already have the resolve function from the above line, because of lexical scope.
You don't call resolve, that keeps the awaiting code hanging after the first letter (the promise never gets resolved).
To fix it, put () after resolve!
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(() => {
console.log(message[num]);
resolve();
}, timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage();
}
welcomeMessage();
Problematic snippet #3
There are 2 problems in this code as well:
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func; //Problem 1
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction(resolve, num), timer[num]); //Problem 2
})
};
The same as above; newFunction doesn't call resolve (named fn).
Try to not forget the () when you intend to call a function
That's the opposite of Problem 1. You immediately call newFunction (due to the parentheses after it: (resolve, num)), and pass its return value (undefined) to the setTimeout. Without Problem 1, this would result in immediately logging all letters.
In this case, let setTimeout to call that function internally by removing (resolve, num) after it. To pass parameters to it, setTimeout accepts additional arguments, that it will hand over to its callback (in this case newFunction).
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func();
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction, timer[num], resolve, num);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage()
}
welcomeMessage();
All together...
It is possible to combine these fixes, to get something like:
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func();
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction, timer[num], resolve, num);
})
};
(async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
})();
}
welcomeMessage();
Conclusion
Use parentheses (()) to call a function, but avoid them to use the function as an object: pass or assign it to something, get or set its properties, etc.
Related
I'm a complete beginner at JavaScript. I just want to call the function called seconONe() just after the function firstOne() completes its execution. by saying this, I mean the function two will call when the value of that p1 is 4 ( in this case ); I can achieve it by calling a setTimeout() function. but what if I don't know how many does it take to execute { the first one() }?
// getting DOM element
const p1 = document.getElementById(`one`);
const p2 = document.getElementById(`two`);
const p3 = document.getElementById(`three`);
// first function
function firstOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p1.innerHTML = i;
}, i * 1000);
}
}
// second function
function seconOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p2.innerHTML = i;
}, i * 1000);
}
}
A possible solution is to work with promises. More info about promises here.
Working example
var p1 = 1;
var p2 = 2;
var p3 = 3;
const firstPromise = new Promise((resolve, reject) => {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p1 = i;
}, i * 1000);
}
resolve()
});
const secondPromise = new Promise((resolve, reject) => {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p2 = i;
}, i * 1000);
}
resolve()
});
//run first promise
console.log("First promise called")
firstPromise
.then((response) => {
console.log("First promise done")
//run second promise after first promise succeed
console.log("Second promise called")
secondPromise
.then((response) => console.log("Second promise done"))
})
your question is not childish at all. What you will need to understand are callbacks and promise handlers. This just tells JavaScript to wait till a task has been completed in order to execute the next task.
firstOne().then(() => secondOne())
put if condition in your firstOne function.
const p1 = document.getElementById(`one`);
const p2 = document.getElementById(`two`);
const p3 = document.getElementById(`three`);
// first function
function firstOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
if(i == 4){
seconOne();
}else{
p1.innerHTML = i;
}
}, i * 1000);
}
}
// second function
function seconOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p2.innerHTML = i;
}, i * 1000);
}
}
Just to build on the other answers that have suggested using a Promise, here's a more generalised solution that also uses async/await.
(In summary: call a function with a count, and an element. That function will return a promise that "at some point" work will be completed. An inner function loops updating the element content until that count has been reached, at which point the promise resolves, and the next thing can start).
// Cache the elements
const p1 = document.querySelector('#one');
const p2 = document.querySelector('#two');
const p3 = document.querySelector('#three');
// `timer` accepts a count, and the element
// to apply the count to
function timer(count, el) {
// Return a promise that basically says:
// once I'm done doing this work, resolve,
// and then the event queue can
// get on with the next thing
return new Promise(resolve => {
// So we create a loop that logs the numbers
// in our element up to the count we specified.
// and when that number is reached, resolve the promise
function loop(n = 0) {
// If our current `n` value is <= count
if (n <= count) {
// Set the content of the element
el.textContent = n;
// Call `loop` again after a second
// with an incremented `n` value
setTimeout(loop, 1000, ++n);
// Otherwise resolve the promise
} else {
resolve();
}
}
loop();
});
}
// And now we just await each resolved promise
async function main() {
await timer(4, p1);
await timer(7, p2);
await timer(20, p3);
console.log('Done!');
}
main();
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
Additional documentation
querySelector
const sleep = (time) => {
return new Promise((resolve) => setTimeout(resolve, time))
}
const doSomething = async () => {
for (let i = 0; i < 100; i++) {
await sleep(1000)
console.log(i)
}
}
doSomething()
Why is this promise working as expected? I'm learning about Promise and stuck on this. The function passed into the Promise constructor does not have reject() and yet it is working.
The snippet is from this article: https://medium.com/javascript-in-plain-english/javascript-slow-down-for-loop-9d1caaeeeeed
I'm also struggling to understand the explanation in the article about the differences between the setTimeout approach (that does not work) vs. setTimeout in promise approach (that works).
Thanks for any pointer on this topic.
Arguments are optional in JavaScript so the reject argument is just ignored.
As for why the first example doesn't work is that setTimeout registers an event for the event loop to call in the future. Things in JavaScript do not block. Internally await rewrites the code so that it does not block, but the code still reads as synchronous, but it is not.
There are plenty of great examples out there explaining the JavaScript event loop, and I'd highly suggest reading up on it.
The Promise is not resolved until the resolve ("res") function is called. The function that gets passed to setTimeout does not run until the timeout finishes.
Here is an alternative example, where the resolve function returns the count that gets passed-in.
const main = async () => {
const done = await doSomething(10, 1000, i => console.log(i));
console.log('Done!');
};
const doSomething = async (times, duration, callback) => {
for (let counter = 0; counter < times; counter++) {
const count = await sleep(counter, duration);
callback(count);
}
return true;
};
const sleep = (count, duration) => {
return new Promise((res) => setTimeout((n) => res(n), duration, count));
};
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }
Here is a less-coupled version, with some default parameters.
const DEBUG = true;
const main = async () => {
const worker = (n, m) => console.log(`Step ${n + 1}/${m}`);
await doSomething(worker);
};
const doSomething = async (callback, duration = 1000, times = 10) => {
if (DEBUG) console.log('Start!');
for (let counter = 0; counter < times; counter++) {
await sleep(duration, counter, times);
callback(counter, times);
}
if (DEBUG) console.log('Done!');
return true;
};
const sleep = (duration, count = 1, times = 1) => {
return new Promise(res => setTimeout(res, duration));
};
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }
I found this code in a project:
const fn = async () => {
let x = 0;
for(let i = 0; i < 50; i++){
const res = await api.call(i);
if(res.someProp) x++;
}
return x;
}
I want to be able to stop it mid way, so that if I call it again, it will start from scratch and discard the previous call results. To avoid making two sets of requests at the same time.
This should do:
let token;
const fn = async () => {
const my = token = Symbol();
let x = 0;
for(let i = 0; i < 50 && my == token; i++){
const res = await api.call(i);
if(res.someProp) x++;
}
return x;
}
While there still can be some overlap between the calls, any previous loops will break their iteration as soon as the next fn() call is started.
You can use any technique of using an external flag variable to break the loop.
As a workaround you can try to use a custom Promise class (Live demo):
import CPromise from "c-promise2";
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function api(i) {
console.log(`Async API call [${i}]`);
await delay(100);
return {};
}
const fn = () =>
CPromise.from(function* () {
let x = 0;
for (let i = 0; i < 50; i++) {
const res = yield api.call(i);
if (res.someProp) x++;
}
return x;
});
const cancelablePromise = fn().then(
() => console.log("Done"),
(err) => console.log(`Fail: ${err}`) // Fail: CanceledError: canceled
);
setTimeout(() => {
cancelablePromise.cancel(); // abort the async sequence (loop) after 3500ms
}, 3500);
In a function run1 random value is generated & stored in random_var, await is used to delay and then random_value is resolved.
When running run1 asynchronously using Promise.all the random_var is changed when logging after await statement as shown in the code snippet demo
main()
async function main() {
await Promise.all([run1(), run1(), run1()]).then(value => {
console.log({ values: value })
})
}
async function run1() {
return new Promise(async (resolve, reject) => {
random_var = makeid(6)
console.log('Logging 1st time has different values', random_var)
await new Promise(resolve => setTimeout(resolve, 500))
console.log('Logging 2nd time has same values', random_var)
resolve(random_var)
})
}
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
This has to do with hoisting and scope.
You line random_var = makeid(6) doesn't use let or const so it gets hoisted to the global scope where it will not change when the promises resolve.
changing it to const random_var = makeid(6) (or let or var, but best practice is const here because you're not going to ever mutate it) should resolve the issue.
main()
async function main() {
await Promise.all([run1(), run1(), run1()]).then(value => {
console.log({ values: value })
})
}
async function run1() {
return new Promise(async (resolve, reject) => {
const random_var = makeid(6)
console.log('Logging 1st time has different values', random_var)
await new Promise(resolve => setTimeout(resolve, 500))
console.log('Logging 2nd time has same values', random_var)
resolve(random_var)
})
}
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
Goal is to push sampled data, as an object, onto an array, at a periodic interval and wait to log the new array out to the console once it is finalized.
I'm new to JS, so take it easy ;). I am likely making this more complicated than it needs to be. Thought it would be as simple as a setTimeout() in a for loop.
I have been able to generate the array two different ways, using IIFE with a setTimeout() also the setInterval() below. Not sure how to get the async await function working with an array push() method querying length. Maybe this is not a good approach?
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
function arrayGenerator(tag){
return sampleArr.push(new Sample(tag));
};
function setIntSample(callback, delay, iterations) {
var i = 0;
var intervalID = setInterval(function () {
callback(i);
if (++i === iterations) {
clearInterval(intervalID);
}
}, delay);
};
Above seems to work console.log()-ing the array as it is generated in the arrayGenerator() function. Below, no dice
function resolveAfterArrGeneration(){
return new Promise(resolve => {
arrLength = setIntSample(i => {arrayGenerator(i)}, 3000, 5)
if (arrLength === 5) {resolve();}
});
}
async function ans() {
var answer = await resolveAfterArrGeneration();
console.log(sampleArr);
}
ans();
The basic idea is to return a promise and resolve the promise when the setInterval has run enough iterations. You can do that in a single function with something like this (with extra console.logs to show the process):
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
function makeSamples(iterations, delay){
let samples = [], i = 0;
return new Promise(resolve => {
let intervalID = setInterval(function () {
console.log("pushing new sample")
samples.push(new Sample('tag: ' + i));
if (++i === iterations) {
console.log("finished resolving")
clearInterval(intervalID);
resolve(samples)
}
}, delay);
})
}
makeSamples(5, 1000).then(console.log)
I would isolate the delay part (the asynchronous) part and create a separate, generic function delay() for that. All the rest becomes simple then, using an async function and for loop:
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
async function setIntSample(callback, ms, iterations) {
const arr = [];
for (let i = 0; i < iterations; i++) {
if (i) await delay(ms); // don't delay first time
arr.push(callback(i));
}
return arr;
}
const newSample = (tag) => new Sample(tag)
console.log("wait for it....");
setIntSample(newSample, 1000, 5).then(console.log);
Another way I just got working with a generator function
function* simpleGenerator(){
var index = 0;
while (true)
yield {tag: index++, time: Date.now()}
}
var gen = simpleGenerator();
..with the corresponding push
arr.push(gen.next().value);