Hello I need help on my code. I am not really familiar with Promises. I created a function called EmitRandomNumber(). In this function, after 2 full seconds (2000 ms), it generates a random number between 0 to 100. If the random number generated is below 80, I need to call that function again, up to 10 times, until the random number generated is greater than 80.
let attempt = 1;
let rN;
function EmitRandomNumber() {
return new Promise((resolve, reject)=> {
console.log(`Attempt #${attempt}. EmitRandomNumber is called.`);
setTimeout(()=>{
let randomNumber = Math.floor(Math.random() * 100) + 1;
rN = randomNumber;
console.log("2 seconds have passed.");
if(randomNumber>=80&&attempt<=10){
console.log(randomNumber,attempt);
resolve();
}else if(randomNumber<80&&attempt<=10){
attempt++;
console.log(`Random number generated is ${randomNumber}.`);
console.log("===============================");
EmitRandomNumber();
}
},2000);
});
}
let promise = EmitRandomNumber();
promise.then(()=>{
console.log(`Random number generated is ${rN}!!!!`);
console.log("===============================");
}).catch(()=>{
console.log("End");
});
I dont know if I am using the promise properly and sometimes when it is above 80 it doesnt execute whatever code is in the resolve. Can you help me how I can fix my code. Thank you!
So the code is mostly good, you just did not specify when to 'reject' the promise. From what you described you want this to 'reject' (or display console.log('End')) IF the promise does not find a number after 10 attempts. So you only needed to add a condition (if attempt===10) etc.
EDIT: The resolve and reject need to be returned.
let attempt = 1;
let rN;
function EmitRandomNumber() {
return new Promise((resolve, reject)=> {
console.log(`Attempt #${attempt}. EmitRandomNumber is called.`);
setTimeout(()=>{
let randomNumber = Math.floor(Math.random() * 100) + 1;
rN = randomNumber;
console.log("2 seconds have passed.");
if(randomNumber>=80&&attempt<=10){
console.log(randomNumber,attempt);
return resolve();
}else if(randomNumber<80&&attempt<=10){
attempt++;
console.log(`Random number generated is ${randomNumber}.`);
console.log("===============================");
EmitRandomNumber();
//CHECK FOR CONDITION NUMBER OF ATTEMPTS
} else if(attempt>10){
return reject();
}
},2000);
});
}
let promise = EmitRandomNumber();
promise.then(()=>{
console.log(`Random number generated is ${rN}!!!!`);
console.log("===============================");
}).catch(()=>{
console.log("End");
});
It may be easier to read and reason about using async functions and await. E.g.:
const MAX_TRIES = 10;
const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
}
const getRandomNumber = () => {
return Math.floor(Math.random() * 100) + 1;
}
const getNumberWaitAndReturn = async () => {
let rN = getRandomNumber();
let attempt = 1;
while (rN < 80 && attempt <= MAX_TRIES) {
await sleep(2000);
rN = getRandomNumber();
attempt += 1;
}
if (attempt > MAX_TRIES) {
console.log(attempt)
throw new Error("No luck! It took more than 10 tries!")
}
return {
rN,
attempt
}
}
const result = getNumberWaitAndReturn().then(v => {
console.log(v)
});
You doing pretty good, you just need a little more practice to do it better, I highly recommend you start using closure instead of declaring global variables as you are doing into your code.
Before tell you how you can solve this, I will tell you that are the error, your function will only work as you expect if the first time its executed it generates a random number above of 80, otherwise the function will never be resolved, why ? that's because everytime you are calling the EmitRandomNumber() into the function itself you are creating a new Promise reference so the first one that you created is never handled because you create a new reference promise, so the new reference, it's no handle because your are calling inside the function itself and nobody is doing .then(...).
So how to solve this ? First at all you need to create a closure to create this.
const emitRandomNumber = () => {
const MAX_ATTEMPTS = 10;
const MS_START_TIME = 2000;
const MINIMUM_NUMBER = 80;
const MAXIMUM_NUMBER = 100;
let attempts = 0;
const randomNumber = (max) => Math.floor(Math.random() * max);
return new Promise((resolve, reject) => {
const startRandomNumber = () => {
attempts++;
const number = randomNumber(MAXIMUM_NUMBER);
if (number < MINIMUM_NUMBER && attempts <= MAX_ATTEMPTS) {
return setTimeout(startRandomNumber, MS_START_TIME);
}
if (number < MINIMUM_NUMBER && attempts > MAX_ATTEMPTS) {
return reject();
}
resolve(number);
};
setTimeout(startRandomNumber, MS_START_TIME);
});
}
As you can see we are using closure to create function inside function body, so the only function that we call again it's the startRandomNumber function, so this won't generate new promise instances, instead we are keeping the same promise reference and we just execute the random number function until we reach the attempts.
Related
I have a loop which resets a timeout every time it completes an iteration, but if the loop takes too long I want to throw an error that is caught by an exception handler from the calling function. How can I throw the error in the parent instead of inside the timeout scope?
Where I'm applying this is attempting to access web pages and if not I have a set of instructions to reset the connection, but hopefully this mwe makes enough sense as a standalone example.
const wait = ms => new Promise(r => setTimeout(r, ms));
const getRand5 = () => Math.floor(Math.random() * 5 + 1);
const wait5 = () => wait(getRand5() * 1e2);
async function something() {
for (let i = 0; i < 10; i++) {
//I want this thrown error to throw an error for function something, not the timeout scope
const timer = setTimeout(() => { throw new Error("too long!"); }, 3e2);
await wait5();
clearTimeout(timer);
}
}
(async () => {
let fails = 0;
do {
try {
console.log("attempt number ", fails);
await something();
break;
} catch (e) {
fails++;
}
} while (fails < 10);
if (fails >= 10) {
console.log("failed too many times");
} else {
console.log("Looks like we gottem boys");
}
})();
You could solve this using Promise.race() with (of course) Promises.
Example:
// Timeout helper function
// Returns a promise which gets resolved after `ms` milliseconds
async function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms, "timed_out");
});
}
async function something() {
// Race Timeout and Wait5 promises here
// If Timeout promise resolves, it will
// resolve the value "timed_out"
const result = await Promise.race([
timeout(3e2),
wait5(),
]);
// Check resolved value here; if timed out, throw error
if (result === "timed_out") {
throw new Error("too long!");
}
}
From some comments this is how I solved the problem in my specific situation. Instead of trying to throw an error, I changed the function from async to returning a promise, and used the reject function to exit early if needed.
const wait = ms => new Promise(r => setTimeout(r, ms));
const getRand5 = () => Math.floor(Math.random() * 5 + 1);
const wait5 = () => wait(getRand5() * 1e2);
function something() {
return new Promise(async (resolve, reject) => {
for (let i = 0; i < 10; i++) {
//I want this thrown error to throw an error for function something, not the timeout scope
const timer = setTimeout(() => { reject() }, 4e2);
await wait5();
clearTimeout(timer);
}
resolve();
});
}
(async () => {
let fails = 0;
do {
try {
if (fails % 10 == 0)console.log("attempt number ", fails);
await something();
break;
} catch (e) {
fails++;
}
} while (fails < 100);
if (fails >= 100) {
console.log("failed too many times");
} else {
console.log("Looks like we gottem boys");
}
})();
I'm trying to print one letter after another but this code just waits 100ms and then prints the value with no pauses. Any idea why this happens and how to fix it?
Code:
for (let i = 0; i < welcText.length; i++) {
setTimeout(()=>{
welcome.innerText += welcText[i];
},100);
}
Note that setTimeout does not actually delay the running of the code that comes after; it just schedules for something to happen in the future. So all the letters are added at the same time, after 100 milliseconds.
The most straightforward way to fix it is to make each subsequent timeout wait a bit longer, by multiplying the delay with the index of the letter (i * 100):
const welcome = document.getElementById('welcome');
const welcText = 'Welcome!';
for (let i = 0; i < welcText.length; i++) {
setTimeout(() => {
welcome.innerText += welcText[i];
}, i * 100);
}
<div id="welcome"></div>
The following function divtxt() returns a Promise. You can use it to send a txt to a div with a given id and let each letter appear in ms intervals. AND: you can build a chain of any number of follow-up actions with it:
function divtxt(id, txt, ms, wait = 0) {
const div = document.getElementById(id);
return new Promise((res, rej) => {
setTimeout(() => { // optional initial timeout, when wait>0
div.textContent = "";
const a = txt.split(""),
iv = setInterval(() => {
if (a.length)
div.textContent += a.shift();
else {
clearInterval(iv);
res(txt);
}
}, ms);
}, wait);
});
}
divtxt("welcome", "Hello world, this is my way of doing it! 🤩", 100)
.then(prevText => (console.log(prevText + ' is done.'),
divtxt("descr", "You can also chain this function with any number of consecutive actions."
+" Now: wait for 2 seconds ...", 100)))
.then(() => divtxt("welcome", "This promised-based approach REALLY lets you do it!! 👍🏻", 50, 2000))
.then(() => (console.log("ready?"),"Yes! I am REALLY DONE now! 😁"))
.then(console.log)
#welcome {
font-weight: 900
}
<div id="welcome"></div>
<div id="descr"></div>
You can do it using setInterval instead, and using an iterator to call the next char of the string every time. This is a bit more complicated, but has the advantage of not having multiple schedulers running if the string is too long. See the working example below.
const welcome = document.getElementById('welcome');
const welcomeText = 'Welcome!';
// returns a function that returns the next char in the string everytime its called
function createIterator(string) {
let currentIndex = 0;
return function() {
return string[currentIndex++];
}
}
// initializes the iterator with the text
let welcomeIterator = createIterator(welcomeText);
let welcomeInterval = setInterval(function() {
let nextChar = welcomeIterator();
// if we finish the string we clear the interval
if (!nextChar) {
return clearInterval(welcomeInterval);
}
// if the char exists, we append it to the div
welcome.innerText += nextChar;
}, 100);
<div id="welcome"></div>
you can do it like this also
const welcText = 'Welcome!';
let i = 0;
const interval = setInterval(() => {
if (i >= welcText.length) {
clearInterval(interval);
} else {
document.getElementById('helloword').innerHTML += welcText[i];
i++;
}
}, 100);
<h1 id="helloword"></h1>
let cancelDownload = true
const delay = () => {
return new Promise(() => {
if (cancelDownload === true){
setTimeout(() => {
console.log('Delaying...');
delay();
}, 1000);
}
else return null;
});
};
const cancelJob = async() => {
for(let i = 6; i>0; i--){
console.log('inside for ',i);
setTimeout(()=>{
cancelDownload = false
},4000)
await delay()
console.log('aaaaaaaa');
console.log(`the number is ${i}`);
}
}
cancelJob()
I am trying to write a delay function whereby once the condition is met delay is removed and all code is resumed but it seems that once the delay is done the code exits without executing the last two console logs
No recursion needed. Instead, you can:
Use setInteval to check your condition every second.
When the condition is correct, you need to resolve the promise.
Use clearInterval.
let cancelDownload = true
const delay = () => {
let intervalId;
return new Promise((resolve, reject) => {
const check = () => {
if (cancelDownload === true){
console.log('Delaying...');
} else {
clearInterval(intervalId);
resolve();
}
}
//check immediately
check();
//then check every second afterwards
intervalId = setInterval(check, 1000);
});
};
const cancelJob = async() => {
for(let i = 6; i>0; i--){
console.log('inside for ',i);
setTimeout(()=>{
cancelDownload = false
},4000)
await delay()
console.log('aaaaaaaa');
console.log(`the number is ${i}`);
}
}
cancelJob()
This can be generalised a bit in the following fashion - instead of hard-coding the condition, supply it as a callback. Then you can have a delay function without using global variables and it can wait for different things, not just one single variable.
const delayWhile = shouldWait => () => {
let intervalId;
return new Promise((resolve, reject) => {
const check = () => {
if (shouldWait()){
console.log('Delaying...');
} else {
clearInterval(intervalId);
resolve();
}
}
//check immediately
check();
//then check every second afterwards
intervalId = setInterval(check, 1000);
});
};
const cancelJob = async() => {
let cancelDownload = true;
const delay = delayWhile(() => cancelDownload);
for(let i = 6; i>0; i--){
console.log('inside for ',i);
setTimeout(()=>{
cancelDownload = false
},4000)
await delay()
console.log('aaaaaaaa');
console.log(`the number is ${i}`);
}
}
cancelJob()
The function you pass to the promise constructor (the promise executor function) is called with two arguments: a function to use to resolve the promise and a function to reject it (conventionally called resolve and reject). Your code should call one of those functions when your asynchronous work is done.
Yours isn't, so the promise never settles, and your code waits forever at the await.
But there are other issues:
1. If you call delay again, it creates a new promise. Your code using await only has access to the first promise, not the ones created by those recursive calls. There isn't really any reason to use recursion here at all.
2. All calls to the function share the same flag. So if we fix the issue with not fulfilling the promise, the loop does wait, but only once:
let cancelDownload = false;
const delay = () => {
return new Promise((resolve) => {
tick();
function tick() {
// Cancelled?
if (cancelDownload) {
// Yes, fulfill the promise
console.log("Flag is set, fulfilling");
resolve(null);
} else {
// No, wait another second
console.log("waiting 1000 and checking again");
setTimeout(tick, 1000);
}
}
});
};
const cancelJob = async () => {
setTimeout(() => {
console.log("Setting cancelDownload to true");
cancelDownload = true;
}, 4000);
for (let i = 6; i > 0; i--) {
console.log("inside for ", i);
await delay();
console.log("aaaaaaaa");
console.log(`the number is ${i}`);
}
};
cancelJob();
.as-console-wrapper {
max-height: 100% !important;
}
I may be mistaken that that second one is a problem for your use case, though, since in a comment on a now-deleted answer you said you wanted the loop only to wait once (the "whole loop" rather than just one iteration).
If you want a function that polls a flag (I don't recommend it, polling is generally not best practice, though sometimes you can't avoid it) and fulfills a promise when it's set, you could use AbortController:
const delay = (signal) => {
let done = false;
return new Promise((resolve) => {
tick();
function tick() {
// Cancelled?
if (signal.aborted) {
// Yes, fulfill the promise with null
console.log("Fulfilling with null");
resolve(null);
} else {
// No, wait another second
console.log("Waiting 1000 and checking again");
setTimeout(tick, 1000);
}
}
});
};
const cancelJob = async () => {
const controller = new AbortController();
setTimeout(() => {
console.log("Cancelling");
controller.abort();
}, 4000);
for (let i = 6; i > 0; i--) {
console.log("inside for ", i);
await delay(controller.signal);
console.log("aaaaaaaa");
console.log(`the number is ${i}`);
}
};
cancelJob();
.as-console-wrapper {
max-height: 100% !important;
}
That also only delays the loop once, because we're passing the same signal to all of the delay functions. Originally I was creating that inside the loop, like this:
const delay = (signal) => {
let done = false;
return new Promise((resolve) => {
tick();
function tick() {
// Cancelled?
if (signal.aborted) {
// Yes, fulfill the promise with null
console.log("Fulfilling with null");
resolve(null);
} else {
// No, wait another second
console.log("Waiting 1000 and checking again");
setTimeout(tick, 1000);
}
}
});
};
const cancelJob = async () => {
for (let i = 6; i > 0; i--) {
const controller = new AbortController();
setTimeout(() => {
console.log("Cancelling");
controller.abort();
}, 4000);
console.log("inside for ", i);
await delay(controller.signal);
console.log("aaaaaaaa");
console.log(`the number is ${i}`);
}
};
cancelJob();
.as-console-wrapper {
max-height: 100% !important;
}
...but then I saw the "whole loop" comment on the other answer.
Note: Normally when you have an asynchronous process with a cancel feature like that, it rejects on cancel with a cancel-specific rejection reason rather than fulfilling, but you get the idea.
One approach to cancellable promises is to supply the promise creator function with a writable parameter like cancelToken. The promise creator populates this parameter with a callback, which, when invoked, cancels this particular promise. This usually leads to simpler and more linear code.
let pause = () => new Promise(r => setTimeout(r, 100));
async function job(name, steps, cancelToken) {
let cancelled = false;
cancelToken.callback = () => cancelled = true;
for (let step = 1; step <= steps; step++) {
console.log(name, step);
await pause();
if (cancelled) {
console.log('CANCELLED');
break;
}
}
}
async function main() {
let cancelToken = {};
let job1 = job('ok', 5, cancelToken);
await job1;
let job2 = job('bad', 5, cancelToken);
setTimeout(cancelToken.callback, 100);
await job2;
}
main()
As a general hint, it's better to avoid new Promise and callbacks whether possible. Note how in this example Promise and setTimeout are only used for testing purposes (simulate real work) and the rest of the code won't change once we replace pause with something more useful like fetch.
I have a list of values that I wanna return asynchronously, but a limited number of values at a time.
So I call my function once and it has an array of say 20 distinct elements say [1,2,...,20], and it keeps on returning 5 elements every second. So I want:
[1,2,..,5] at 0 sec,
[6,7,..,10] at 1 sec,
and so on....
I was thinking on using buffer files. If it is a valid solution, can someone tell me how to implement it for the given example?
An async generator function (or method) can yield (loosely, return) a sequence of values over time. Here's an example:
function sleepRandom(maxMs) {
return new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * maxMs)));
}
async function *example(max) {
yield -1; // Example of explicit value
// Wait 500ms
await sleepRandom(800);
// Example of loop
for (let i = 0; i < max; ++i) {
// Wait 500ms
await sleepRandom(800);
// Yield current loop value
yield i;
}
// All done
yield "Done";
}
(async () => {
for await (let value of example(10)) {
console.log(value);
}
})()
.catch(error => {
console.error(error);
});
The * after function is what makes it a generator function. The async before function is what makes it async.
You could use nodes EventEmitter for that:
var events = require('events');
function spitChunks(arr, chunksize = 5, delay = 1000, em = null) {
if(!em) em = new events.EventEmitter();
if(arr.length > 0) setTimeout(() => {
em.emit('nextChunk', arr.splice(0,chunksize));
spitChunks(arr, chunksize, delay, em);
}, delay);
return em;
}
let numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
let emitter = spitChunks(numbers);
emitter.on('nextChunk', data => console.log(data));
I have a promise function which successfully can return a random integer between a min and max value.
const getRandomValue = (min = 0, max = 1) => {
return new Promise(function(resolve, reject){
let randomInteger = Math.floor(Math.random() * (max - min)) + min;
resolve(randomInteger);
});
};
I am trying to asychronously fill an array using this promise function, with 4 random integers.
The chronology of the calls is a challenge for me and I am brand new to this type of programming. Below is what I have been trying to get to work without success.
const getFourRandomValues = (min, max) => {
let randomIntegerArray = [];
for(let i = 0; i < 4; i++){
getRandomValue(20,30).then(x => console.log(x));
getRandomValue(20,30).then(x => {
randomIntegerArray.push(x)
});
}
return randomIntegerArray;
};
console.log(getFourRandomValues());
As you experienced programers surely know, it yields:
[]
22
29
25
21
All help is much appreciated
Notice, that if you execute asynchronous operations, the will be resolved or rejected only after your synchronous code, that's why in your example you get an empty array. You can easily avoid it using Promise.all, I recommend it for your case because you're using multiple asynchronous calls.
The logic is the following:
Push every asynchronous call into array.
Call Promise.all(arrayOfPromises)
Handle the result via .then() block (inside then you have an access to the result of every call if each of them was resolved successfully)
Here is an example:
const getRandomValue = (min = 0, max = 1) => {
return new Promise(function(resolve, reject){
let randomInteger = Math.floor(Math.random() * (max - min)) + min;
resolve(randomInteger);
});
};
const getFourRandomValues = (min, max) => {
let promises = [];
for(let i = 0; i < 4; i++) {
promises.push(getRandomValue(20,30));
}
return Promise.all(promises);
}
getFourRandomValues()
.then(x => console.log(x));
Your getFourRandomValues method is doing an async operation. Since then, it should be an async function as well. The [] is returned in your console because you are trying to output the array before the promises inside it has been resolved.
To fix it, you can change getFourRandomValues method to return a promise, that will be resolved when the array is ready:
const getFourRandomValues = async(min, max) => {
let randomIntegerArray = [];
for(let i = 0; i < 4; i++){
const randomValue = await getRandomValue(20,30);
randomIntegerArray.push(randomValue)
}
return randomIntegerArray;
};
getFourRandomValues().then((data) => {
console.log(data);
});
In this example I used async function for better code readability.