I'm practicing with some Promises and closures. I have a forEach loop where I return a Promise with a 3 second timeout and after the Promise resolves, it should log a statement.
I think I'm doing this incorrectly because I'm expecting every 3 seconds to see a log of "111" followed by "222" however I am seeing a delay of 3 seconds then immediately 3 logs of "111" "222".
let arr = [1,2,3];
arr.forEach((x,i) => {
(function() {
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log("111")
resolve(true)
}, 3000);
})
})()
.then(() => {console.log("222")})
});
You simply forgot to tell javascript to "await" for that timeout between each iteration of the for loop. So what's happening it that javascript will run the for loop, schedule three timeouts while doing so, then those three timeouts all go off at once.
If you add an await like so, then it'll work as you expected.
(async function() {
let arr = [1, 2, 3];
for (let x of arr) {
await (function() { // <-- await added
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("111")
resolve(true)
}, 3000);
})
})()
.then(() => {
console.log("222")
})
}
})()
I switched to for-of, because .forEach() doesn't work with async functions. I also wrapped the whole thing inside an async IIFE because a top-level await wasn't allowed there - depending on where you put this code, you might not have to wrap it inside an async IIFE.
EDIT
Just realized, you didn't use async/await stuff anywhere in your original question. I don't know if you've learned about it yet but you don't have to know it to solve this particular problem.
Here's another way to do it without async/await.
let arr = [1, 2, 3];
let promise = Promise.resolve();
arr.forEach((x,i) => {
promise = promise
.then(function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("111")
resolve(true)
}, 3000);
})
})
.then(() => {
console.log("222")
})
});
This is basically building a promise chain inside the loop. If you "unwound" the loop, it would look like this:
promise = Promise.resolve()
promise = promise
.then(() => /* wait 3000 ms and log "111" */)
.then(() => { console.log("222") })
.then(() => /* wait 3000 ms and log "111" */)
.then(() => { console.log("222") })
.then(() => /* wait 3000 ms and log "111" */)
.then(() => { console.log("222") })
Because we're keeping around the a reference to the last promise, and we keep tacking onto the end of it, each new thing we tack on will happen after the last thing gets completed.
Related
I have a question about the async/await execution.
example codes
async firstMethod(){
new Promise((resolve, reject)) => {
setTimeout(() => {
resolve("test1");
}, 3000);
});
}
async secondMethod() {
new Promise((resolve, reject)) => {
setTimeout(() => {
resolve("test2");
}, 1000);
});
}
await firstMethod();
await secondMethod();
So, When the two methods are executed, the following results are obtained.
test2
test1
However, if a return is attached, the result value is as follows.
async firstMethod(){
return new Promise((resolve, reject)) => {
setTimeout(() => {
resolve("test1");
}, 3000);
});
}
async secondMethod() {
return new Promise((resolve, reject)) => {
setTimeout(() => {
resolve("test2");
}, 1000);
});
}
await firstMethod();
await secondMethod();
test1
test2
Why is that? I'd appreciate it if you could explain it.
In the first case you just await the outer promises (that are the firstMethod and secondMethod functions) and the inner promises created by new Promise do not get awaited, and simply run to completion in the background.
In the second case, because of promise unwrapping you await the returned inner promises together with the outer function promises.
The reason why in the first example those 2 functions do not run in order is because you are not waiting for anything. If you try to remove the async from the function definition in the first example you will get the same result because the function does not need the async because it does not require an await. What you could do to have the same result has the second example is by adding a await before the new Promise.
Note: You also do not need the async in the second example because you are not waiting for anything, you are just returning a Promise in which you wait outside the scope.
I've found that attempting to test timers with Jest is hard enough without getting async/await involved.
I have a setInterval that's wrapped in a promise. The callback of the setInterval only resolves if the condition is matched for it to resolve (shown below). If the promise is not resolved, it keeps attempting the iteration until it is.
index.js
module.exports = async function() {
let interval
const _promise = new Promise((resolve, reject) => {
interval = setInterval(async () => {
try {
const result = await asyncTask()
if (result && result.val === 'Foo Bar') {
clearInterval(interval)
resolve(result)
}
} catch(err) {
reject(err)
}
}, 10000) // 10 secs
})
const result = await _promise
if (result) {
return result
}
}
I've mocked the return of asyncTask with something like:
__mocks__/asyncTask.js
module.exports = jest.fn()
.mockReturnValueOnce()
.mockReturnValueOnce()
.mockReturnValueOnce({
val: 'Hello World'
})
.mockReturnValue({
val: 'Foo Bar'
})
What I am attempting to test is the callback of each iteration of the setInterval & I want to try to control that all operations in the callback are finished before I advance the timer again & the next setInterval happens. For example, with the mock above, a truthy result will be returned on the 3rd invocation of that function. The 4th invocation would also be a truthy result & would match the condition for the interval to be cleared & the promise to be resolved. 1 test I would look to do, for example, is have the setInterval iterate 3 times & even though asyncTask returns an object, the promise is not yet resolved. A few examples of how I've attempted this are below:
index.test.js
jest.useFakeTimers()
const _test = require('./index.js')
describe('test()', () => {
test("Should call 'asyncTask' thrice (1)", () => {
const promise1 = new Promise((resolve, reject) => {
// I cannot await `_test()` here as the fast-forward of the timer would never happen
// So I would actually have to wait 10 seconds
_test().then(resolve)
jest.advanceTimersByTime(10000)
})
const promise2 = new Promise((resolve, reject) => {
_test().then(resolve)
jest.advanceTimersByTime(10000)
})
const promise3 = new Promise((resolve, reject) => {
_test().then(resolve)
jest.advanceTimersByTime(10000)
})
// I have to do it like this over a `Promise.all()` I imagine as I can't risk having them run in parallel
// I imagine that has the potential to mess up the mock function call count of `asyncTask` so would not be reliable
const result1 = await promise1
const result2 = await promise2
const result3 = await promise3
expect(result1).toBeUndefined()
expect(result2).toBeUndefined()
expect(result3).toBeUndefined()
})
test('Should call `asyncTask` thrice (2)', async () => {
_test().then(res => {
expect(res).toBeInstanceOf(Object)
})
await jest.advanceTimersByTime(10000)
await jest.advanceTimersByTime(10000)
await jest.advanceTimersByTime(10000)
await jest.advanceTimersByTime(10000)
})
})
The latter was probably closer to what I want to achieve as in the former example; promise1, promise2, & promise3 will never resolve so the test will never complete.
I can test this successfully is I use real timers (jest.useRealTimers()) instead of fake ones but the obvious issue is I would have to wait 40 secs for the promise to resolve & the test to complete.
Is there any clean way to achieve what I want here? Any help appreciated :)
In Promise.race the promise returns as soon that the primary promise returns. In Promise.all returns when all promises resolves, but lasts one problem. If any of all promises rejects all others will be rejected.
Instead of it, exists a proposal for a Promise.any, the returns every promise alone, independent of each other, short-circuiting on a rejection.
const logAfterWait = (seconds) => new Promise((resolve, reject) => {
return setTimeout(() => resolve(console.log(`${time} time passed`)), seconds)
})
const watingList = [
logAfterWait(convertToSeconds(10)),
logAfterWait(convertToSeconds(30)),
logAfterWait(convertToSeconds(5))
]
const logReading = async (fn) => {
console.log(`${time}: reading file`)
await fn()
}
const readFiles = (files) => Promise.all(watingList.map(logReading))
.catch((error) => new Error(error))
The problem here is the block of event loop on the maping cause block on event loop on Promise.all, returning every results on the same time, differ from the expected result, that is, 5, 10, 30 seconds.
Can I avoid this situation on waitingList.map?
You can leverage the fact that Promise.race forms a monoid by creating a Promise that never settles:
const empty = x => new Promise((res, rej) => x); // never settling promise
const ps = [
Promise.reject(1).catch(empty),
Promise.resolve(2).catch(empty),
Promise.resolve(3).catch(empty)];
Promise.race(ps)
.then(console.log); // 2
You need to attach a catch handler to each Promise in the array though. You can probably create a utility function that does this for you.
You could think of something like this:
// a solution might just be not using async/await
const any = (promises) => new Promise((resolve, reject) => {
let errors = [];
let resolved;
const onFulfill = (value) => {
// skip if already resolved
if (resolved) { return; }
resolved = true;
// resolve with the first available value
resolve(value);
};
const onError = (error) => {
// skip if already resolved
if (resolved) { return; }
// collect error
errors = errors.concat(error);
// reject promise combinator if all promises are failed
if (errors.length === promises.length) {
reject(errors);
}
};
return promises.forEach((promise) => promise.then(
onFulfill,
onError,
));
});
const sleep = (ms) => new Promise(r => setTimeout(() => r(ms), ms));
const err = (ms) => sleep(ms).then(() => Promise.reject(ms));
// it would log 2000, since it is the first to resolve
any([sleep(3000), err(100), sleep(2000)]).then(console.info)
// it would an array of 2 failures
any([err(50), err(60)]).catch(console.error)
the block of IO
Note that there isn't any block of IO in javascript, the thread is just free to tackle any other task while waiting for the promises to be resolved.
Consequently, I came to a conclusion. We create a resolver that is an Either monad(not a pure implementation of the Either monad) that returns [err, response] over a map function.
The catch blocks are necessary to avoid the Unhandled Promise Rejection Warning.
const time = () => `${new Date().getHours()}:${new Date().getMinutes()}:${new Date().getSeconds()}`;
const sleep = (ms, pNumber) => new Promise((resolve, reject) => {
return pNumber < 3
? setTimeout(() => resolve(console.log(`${time()} time passed`)), ms)
: reject(null)
}).catch(null)
Promise.prototype.resolver = async (promise) => {
this._result = await Promise.all([promise])[0];
return this._result == null
? ["The time flies", promise]
: [null, promise]
}
const watingList = [
Promise.resolver(sleep(0, 0).catch(console.error)),
Promise.resolver(sleep(3000, 1).catch(console.error)),
Promise.resolver(sleep(5000, 2).catch(console.error)),
Promise.resolver(sleep(5000, 3).catch(console.error))
]
const logReading = (list) => {
return list.map(p => p.then(console.log(`${time()}: reading file`))
.catch(console.log))
}
((read) => logReading(read))(watingList)
PS: time function differs from the expected because of the evaluate time.
Resources can be found here:
1 - https://frontendmasters.com/courses/hardcore-js-v2/either-monad/
This code works as expected:
services.map(async svc => {
promises.push(new Promise(async (resolve) => {
html += `<h2>${svc}</h2>`;
let journeyDetails = await admin.database().ref(`data`).once('value');
resolve();
}));
});
await Promise.all(promises).then(() => {
return res.send(html);
})
Why does the code below not work? In my eyes it's the same, but the execution order is now incorrect.
Promise.all([
services.map(async svc => {
new Promise(async (resolve) => {
html += `<h2>${svc}</h2>`;
let journeyDetails = await admin.database().ref(`data`).once('value');
resolve();
})
})
]).then(() => {
// done - called before finished in this version
}).catch(() => {
// err
});
I believe the primary reason that your code doesn't work is that you are passing an array of arrays ([services.map(...)]) to Promise.all, not an array of promises.
However, the code is unnecessarily complex. There is no need to create a promise inside an async function, async functions always return a promise. It should just be:
Promise.all( // <- note the removed [...]
services.map(async svc => {
html += `<h2>${svc}</h2>`;
let journeyDetails = await admin.database().ref(`data`).once('value');
// more code here
})
)
how to callback timer() function after forEach loop is finished, using the same code. or is there is a better way to loop through each user with delay then after the loop is done, the timer() is called back using forEach.
const usersProfile = () => {
let interval = 1000;
let promise = Promise.resolve();
db.select('*').from('users')
.returning('*')
.then(users => {
users.forEach((user, index) => {
setTimeout(function(){
}, index * 1000)
db.select('*').from(`${user.name}`)
.returning('*')
.then(userdata => {
userdata.forEach(data => {
promise = promise.then(function(){
if(data.status === 'online'){
console.log(`${data.name} ${data.items} ${data.profile} ${data.images}`)
}
return new Promise(function(resolve){
setTimeout(function(){
resolve();
}, interval)
})
})
})
})
})
timer();
})
}
const timer = () => {
setTimeout(usersProfile, 1000)
}
timer();
===============ALL THE ABOVE ARE MY OLD CODE ================
but thanks to https://stackoverflow.com/users/2404452/tho-vu it solved most of the problem but can i do this to serve the purpose of the app
const usersProfile = async () => {
let interval = 1000;
const delayPromise = (data, delayDuration) => {
return new Promise((resolve) => {
setTimeout(() => {
if(data.status === 'online'){
console.log(`${data.name} ${data.items} ${data.profile} ${data.images}`);
resolve();
}
}, delayDuration)
});
};
const userInfo = (data, delayDuration) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`${data.info}`);//info about user from his table each user has his own table besides users table that has only the user tables
resolve();
}, delayDuration)
});
};
try {
const userData = await db.select('*').from('users').returning('*');
const promises = userData.map((data, index) => delayPromise(data, index * interval));
const userData = await db.select('*').from(`${data.name}`).returning('*');
const info = userData.map((data, index) => userInfo(data, index * interval));
await Promise.all(promises);
// here you can write your effects
} catch (e) {
console.log(e);
}
}
Another approach using async-await to avoid callback hell.
const usersProfile = async () => {
let interval = 1000;
const delayPromise = (data, delayDuration) => {
return new Promise((resolve) => {
setTimeout(() => {
if(data.status === 'online'){
console.log(`${data.name} ${data.items} ${data.profile} ${data.images}`);
resolve();
}
}, delayDuration)
});
};
try {
const userData = await db.select('*').from('users').returning('*');
const promises = userData.map((data, index) => delayPromise(data, index * interval));
await Promise.all(promises);
// here you can write your effects
} catch (e) {
console.log(e);
}
}
it is hard for me to fully understand what you wanted to accomplish through the code, but I will try to explain what you can do to solve the issue.
First of all, if you want to wait for multiple promises, you should use Promise.all and not promise = promise.then
You can do this as so:
let promises = [];
users.forEach((user, index) => {
let promise = db.select(*).from('users') //should return a promise
promises.push(promise);
});
//promises have already started to execute (in parallel)
Promise.all(promises)
.then(() => console.log('all promises finished successfully'))
.catch((e) => console.error('received error at one of the promises'));
// when the program arrives here we know for a fact that either all promises executed successfully or one of the promises failed
timer();
Explanation: because promises execute asynchronously the forEach() function does not wait for any of the promises created to finish, so the solution is to wait for all of them at then after the entire loop.
This means the promises are created, and are being executed while the forEach() loop executes, in parallel, and when the program reaches Promise.all it stops and waits for all of them to finish.
Second, I would strongly advice you to use functions as a way to simplify your code, that way it would be easier for you to grasp every thing that is happening, even if its complicated.
Good luck !