Related
AsyncGenerator.prototype.return() - JavaScript | MDN states:
The return() method of an async generator acts as if a return statement is inserted in the generator's body at the current suspended position, which finishes the generator and allows the generator to perform any cleanup tasks when combined with a try...finally block.
Why then does the following code print 0–3 rather than only 0–2?
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const values = (async function* delayedIntegers() {
let n = 0;
while (true) {
yield n++;
await delay(100);
}
})();
await Promise.all([
(async () => {
for await (const value of values) console.log(value);
})(),
(async () => {
await delay(250);
values.return();
})(),
]);
I tried adding log statements to better understand where the "current suspended position" is and from what I can tell when I call the return() method the AsyncGenerator instance isn't suspended (the body execution isn't at a yield statement) and instead of returning once reaching the yield statement the next value is yielded and then suspended at which point the "return" finally happens.
Is there any way to detect that the return() method has been invoked and not yield afterwards?
I can implement the AsyncIterator interface myself but then I lose the yield syntax supported by async generators:
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const values = (() => {
let n = 0;
let done = false;
return {
[Symbol.asyncIterator]() {
return this;
},
async next() {
if (done) return { done, value: undefined };
if (n !== 0) {
await delay(100);
if (done) return { done, value: undefined };
}
return { done, value: n++ };
},
async return() {
done = true;
return { done, value: undefined };
},
};
})();
await Promise.all([
(async () => {
for await (const value of values) console.log(value);
})(),
(async () => {
await delay(250);
values.return();
})(),
]);
Why does the code print 0–3 rather than only 0–2? From what I can tell, when I call the return() method, the AsyncGenerator instance isn't suspended (the body execution isn't at a yield statement) and instead of returning once reaching the yield statement the next value is yielded and then suspended at which point the "return" finally happens.
Yes, precisely this is what happens. The generator is already running because the for await … of loop did call its .next() method, and so the generator will complete that before considering the .return() call.
All the methods that you invoke on an async generator are queued. (In a sync generator, you'd get a "TypeError: Generator is already running" instead). One can demonstrate this by immediately calling next multiple times:
const values = (async function*() {
let i=0; while (true) {
await new Promise(r => { setTimeout(r, 1000); });
yield i++;
}
})();
values.next().then(console.log, console.error);
values.next().then(console.log, console.error);
values.next().then(console.log, console.error);
values.return('done').then(console.log, console.error);
values.next().then(console.log, console.error);
Is there any way to detect that the return() method has been invoked and not yield afterwards?
No, not from within the generator. And really you probably still should yield the value if you already expended the effort to produce it.
It sounds like what you want to do is to ignore the produced value when you want the generator to stop. You should do that in your for await … of loop - and you can also use it to stop the generator by using a break statement:
const delay = (ms) => new Promise((resolve) => {
setTimeout(resolve, ms);
});
async function* delayedIntegers() {
let n = 0;
while (true) {
yield n++;
await delay(1000);
}
}
(async function main() {
const start = Date.now();
const values = delayedIntegers();
for await (const value of values) {
if (Date.now() - start > 2500) {
console.log('done:', value);
break;
}
console.log(value);
}
})();
But if you really want to abort the generator from the outside, you need an out-of-band channel to signal the cancellation. You can use an AbortSignal for this:
const delay = (ms, signal) => new Promise((resolve, reject) => {
function done() {
resolve();
signal?.removeEventListener("abort", stop);
}
function stop() {
reject(this.reason);
clearTimeout(handle);
}
signal?.throwIfAborted();
const handle = setTimeout(done, ms);
signal?.addEventListener("abort", stop, {once: true});
});
async function* delayedIntegers(signal) {
let n = 0;
while (true) {
yield n++;
await delay(1000, signal);
}
}
(async function main() {
try {
const values = delayedIntegers(AbortSignal.timeout(2500));
for await (const value of values) {
console.log(value);
}
} catch(e) {
if (e.name != "TimeoutError") throw e;
console.log("done");
}
})();
This will actually permit to stop the generator during the timeout, not after the full second has elapsed.
Is there a way to prevent this "extra yield" after invoking the return method? If not, are there libraries, patterns, etc. our there that avoid this while still implementing these AsyncIterator interface optional properties?
As #Bergi clearly explained, the extra yield cannot be avoided with the AsyncGenerator.return() method. This is a really interesting case, but I don't think you will find libraries that fix it. #Bergi proposed a clever solution using the AbortSignal, I have tried a different approach with only Promises:
(async function test() {
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const wrapIntoStoppable = function (generator) {
const newGenerator = {
isGeneratorStopped: false,
resolveStopPromise: null,
async *[Symbol.asyncIterator]() {
let stoppedSymbol = Symbol('stoppedPromise')
let stoppingPromise
while (true) {
if (this.isGeneratorStopped)
return
stoppingPromise = new Promise((resolve, _) => this.resolveStopPromise = resolve)
.then(_ => stoppedSymbol)
nextValuePromise = generator.next()
const result = await Promise.race([nextValuePromise, stoppingPromise])
this.resolveStopPromise() // resolve the promise in case it is still pending
if (result === stoppedSymbol)
return
else
yield result.value
}
},
stop: function() {
this.resolveStopPromise()
this.isGeneratorStopped = true
}
}
const handler = {
get: function(target, prop, receiver) {
if (['next', 'return', 'throw'].includes(prop))
return generator[prop].bind(generator)
else
return newGenerator[prop].bind(newGenerator)
}
}
return new Proxy(newGenerator, handler)
}
const values = wrapIntoStoppable((async function* delayedIntegers() {
let n = 0;
while (true) {
yield n++;
await delay(100);
}
})());
await Promise.all([
(async () => {
for await (const value of values) {
console.log(Date.now())
console.log(value);
}
console.log(Date.now())
// console.log(await values.next())
// console.log(await values.return())
// console.log(await values.throw())
})(),
(async () => {
await delay(250);
values.stop()
})(),
]);
})();
The idea is that I wrap an async generator with an object that has an async iterator. All the elements yielded by the wrapping generator are yielded by the original generator, but now 2 promises are started:
nextValuePromise that will return the value to yield
stoppingPromise that will end the iteration if resolved before the previous one
In this way, if the stop() method (which resolves stoppingPromise) is called before the first promise is resolved, then Promise.race() will immediately return a dummy Symbol. When the result of the race is this symbol, the iterator returns. The stop() function also sets the isGeneratorStopped flag that makes sure the iterator will eventually return if the stop() method is called after the stoppingPromise() is manually resolved.
I have also used a Proxy to make sure that the wrapping object behaves as a true AsyncGenerator. Calls to next(), return() or throw() are simply forwarded to the wrapped generator.
Let's see the pros:
wrapIntoStoppable can become a util method that just wraps any async generator. This is certainly convenient because you don't have to use signals every time there is a pending Promise 1
Once the stop() method is called on the async generator, the for await...of loop immediately returns. Note: this doesn't mean that pending Promises are aborted
And now the cons:
Maybe too much code to maintain? Now the generator has a proxy that wraps another wrapper... I would like to simplify the design at least
After the generator is stopped, the nextValuePromise() could be resolved in the meantime, causing some potential side effects. This is the main reason why it is a pretty dangerous library function.
Actually, I think you could even merge #Bergi's and my solution and manage to abort a Promise when the stop() method is called. However, in this case, all the promises need to handle the abort signals.
await would suspend the async part of the function, but not the generator part, thus AsyncGenerator.return() can not act on the await suspension, but only yield suspension. And I think that's why AsyncGenerator.return() returns a promise, but Generator.return() does not.
Yes. Bergi is right. The for await loop invokes .next() right after the consumption and puts yield in charge before return. So what happens is;
# 0ms 0 gets yielded and .next() puts yield in charge to yield 1 once resolved.
#100ms 1 gets yielded and .next() puts yield in charge to yield 2 once resolved.
#200ms 2 gets yielded and .next() puts yield in charge to yield 3 once resolved.
#250ms a values.return() is enqueued but yield has already been queued to yield 3.
#300ms 3 gets yielded and generator finalizes along with the iterable values.
Now the thing is, if we find a way to resolve or reject the promise waiting for 3 prematurely #250ms then we are fine. Yet without using the abort abstraction you can still do this with naked promises and even without using an async generator. You just need to lift the resolve and reject functions out of the generator functions scope and invoke from there. I think it's best to reject prematurely and catch the rejection at the outer scope (silent or not).
Here is a way to accomplish this;
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
prior = {};
const values = (function* delayedIntegers() {
let n = 0,
p = new Promise((v,x) => Object.assign(prior,{v,x}));
prior.v(n++); // resolve p with 0 and increment n
while (true) {
yield p
p = new Promise((v,x) => Object.assign(prior,{v,x}));
delay(100).then(_ => prior.v(n++));
}
})();
(async () => {
try {
for await (const value of values) console.log(value);
}
catch(e){
console.log(e);
values.return();
}
})();
delay(250).then(_ => prior.x("Finalized..!"));
This is almost like your code but there is this prior object which holds the resolve and reject functions of a promise callback as v and x respectively.
Since prior object is accessible from within the outer context we can invoke it's x method (rejection) before the while loop in the generator resolves 3 and catch the rejection with the employed catch(e).
I'm trying to execute some async tasks in parallel with a limitation on the maximum number of simultaneously running tasks.
There's an example of what I want to achieve:
Currently this tasks are running one after another. It's implemented this way:
export function signData(dataItem) {
cadesplugin.async_spawn(async function* (args) {
//... nestedArgs assignment logic ...
for (const id of dataItem.identifiers) {
yield* idHandler(dataItem, id, args, nestedArgs);
}
// some extra logic after all tasks were finished
}, firstArg, secondArg);
}
async function* idHandler(edsItem, researchId, args, nestedArgs) {
...
let oDocumentNameAttr = yield cadesplugin.CreateObjectAsync("CADESCOM.CPAttribute");
yield oDocumentNameAttr.propset_Value("Document Name");
...
// this function mutates some external data, making API calls and returns void
}
Unfortunately, I can't make any changes in cadesplugin.* functions, but I can use any external libraries (or built-in Promise) in my code.
I found some methods (eachLimit and parallelLimit) in async library that might work for me and an answer that shows how to deal with it.
But there are still two problems I can't solve:
How can I pass main params into nested function?
Main function is a generator function, so I still need to work with yield expressions in main and nested functions
There's a link to cadesplugin.* source code, where you can find async_spawn (and another cadesplugin.*) function that used in my code.
That's the code I tried with no luck:
await forEachLimit(dataItem.identifiers, 5, yield* async function* (researchId, callback) {
//... nested function code
});
It leads to Object is not async iterable error.
Another attempt:
let functionArray = [];
dataItem.identifiers.forEach(researchId => {
functionArray.push(researchIdHandler(dataItem, id, args, nestedArgs))
});
await parallelLimit(functionArray, 5);
It just does nothing.
Сan I somehow solve this problem, or the generator functions won't allow me to do this?
square peg, round hole
You cannot use async iterables for this problem. It is the nature of for await .. of to run in series. await blocks and the loop will not continue until the awaited promise has resovled. You need a more precise level of control where you can enforce these specific requirements.
To start, we have a mock myJob that simulates a long computation. More than likely this will be a network request to some API in your app -
// any asynchronous task
const myJob = x =>
sleep(rand(5000)).then(_ => x * 10)
Using Pool defined in this Q&A, we instantiate Pool(size=4) where size is the number of concurrent threads to run -
const pool = new Pool(4)
For ergonomics, I added a run method to the Pool class, making it easier to wrap and run jobs -
class Pool {
constructor (size) ...
open () ...
deferNow () ...
deferStacked () ...
// added method
async run (t) {
const close = await this.open()
return t().then(close)
}
}
Now we need to write an effect that uses our pool to run myJob. Here you will also decide what to do with the result. Note the promise must be wrapped in a thunk otherwise pool cannot control when it begins -
async function myEffect(x) {
// run the job with the pool
const r = await pool.run(_ => myJob(x))
// do something with the result
const s = document.createTextNode(`${r}\n`)
document.body.appendChild(s)
// return a value, if you want
return r
}
Now run everything by mapping myEffect over your list of inputs. In our example myEffect we return r which means the result is also available after all results are fetched. This optional but demonstrates how program knows when everything is done -
Promise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(myEffect))
.then(JSON.stringify)
.then(console.log, console.error)
full program demo
In the functioning demo below, I condensed the definitions so we can see them all at once. Run the program to verify the result in your own browser -
class Pool {
constructor (size = 4) { Object.assign(this, { pool: new Set, stack: [], size }) }
open () { return this.pool.size < this.size ? this.deferNow() : this.deferStacked() }
async run (t) { const close = await this.open(); return t().then(close) }
deferNow () { const [t, close] = thread(); const p = t.then(_ => this.pool.delete(p)).then(_ => this.stack.length && this.stack.pop().close()); this.pool.add(p); return close }
deferStacked () { const [t, close] = thread(); this.stack.push({ close }); return t.then(_ => this.deferNow()) }
}
const rand = x => Math.random() * x
const effect = f => x => (f(x), x)
const thread = close => [new Promise(r => { close = effect(r) }), close]
const sleep = ms => new Promise(r => setTimeout(r, ms))
const myJob = x =>
sleep(rand(5000)).then(_ => x * 10)
async function myEffect(x) {
const r = await pool.run(_ => myJob(x))
const s = document.createTextNode(`${r}\n`)
document.body.appendChild(s)
return r
}
const pool = new Pool(4)
Promise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(myEffect))
.then(JSON.stringify)
.then(console.log, console.error)
slow it down
Pool above runs concurrent jobs as quickly as possible. You may also be interested in throttle which is also introduced in the original post. Instead of making Pool more complex, we can wrap our jobs using throttle to give the caller control over the minimum time a job should take -
const throttle = (p, ms) =>
Promise.all([ p, sleep(ms) ]).then(([ value, _ ]) => value)
We can add a throttle in myEffect. Now if myJob runs very quickly, at least 5 seconds will pass before the next job is run -
async function myEffect(x) {
const r = await pool.run(_ => throttle(myJob(x), 5000))
const s = document.createTextNode(`${r}\n`)
document.body.appendChild(s)
return r
}
In general, it should be better to apply #Mulan answer.
But if you also stuck into cadesplugin.* generator functions and don't really care about heavyweight external libraries, this answer may also be helpful.
(If you are worried about heavyweight external libraries, you may still mix this answer with #Mulan's one)
Async task running can simply be solved using Promise.map function from bluebird library and double-usage of cadesplugin.async_spawn function.
The code will look like the following:
export function signData(dataItem) {
cadesplugin.async_spawn(async function* (args) {
// some extra logic before all of the tasks
await Promise.map(dataItem.identifiers,
(id) => cadesplugin.async_spawn(async function* (args) {
// ...
let oDocumentNameAttr = yield cadesplugin.CreateObjectAsync("CADESCOM.CPAttribute");
yield oDocumentNameAttr.propset_Value("Document Name");
// ...
// this function mutates some external data and making API calls
}),
{
concurrency: 5 //Parallel tasks count
});
// some extra logic after all tasks were finished
}, firstArg, secondArg);
}
The magic comes from async_spawn function which is defined as:
function async_spawn(generatorFunction) {
async function continuer(verb, arg) {
let result;
try {
result = await generator[verb](arg);
} catch (err) {
return Promise.reject(err);
}
if (result.done) {
return result.value;
} else {
return Promise.resolve(result.value).then(onFulfilled, onRejected);
}
}
let generator = generatorFunction(Array.prototype.slice.call(arguments, 1));
let onFulfilled = continuer.bind(continuer, "next");
let onRejected = continuer.bind(continuer, "throw");
return onFulfilled();
}
It can suspend the execution of internal generator functions on yield expressions without suspending the whole generator function.
function first(){
console.log('first')
}
function second(){
console.log('second')
}
let interval = async ()=>{
await setInterval(first,2000)
await setInterval(second,2000)
}
interval();
Imagine that I have this code above.
When I run it, first() and second() will be called at the same time; how do I call second() after first)() returns some data, for example, if first() is done, only then call second()?
Because first() in my code will be working with a big amount of data and if this 2 functions will be calling at the same time, it will be hard for the server.
How do I call second() each time when first() will return some data?
As mentioned above setInterval does not play well with promises if you do not stop it. In case you clear the interval you can use it like:
async function waitUntil(condition) {
return await new Promise(resolve => {
const interval = setInterval(() => {
if (condition) {
resolve('foo');
clearInterval(interval);
};
}, 1000);
});
}
Later you can use it like
const bar = waitUntil(someConditionHere)
You have a few problems:
Promises may only ever resolve once, setInterval() is meant to call the callback multiple times, Promises do not support this case well.
Neither setInterval(), nor the more appropriate setTimeout() return Promises, therefore, awaiting on them is pointless in this context.
You're looking for a function that returns a Promise which resolves after some times (using setTimeout(), probably, not setInterval()).
Luckily, creating such a function is rather trivial:
async function delay(ms) {
// return await for better async stack trace support in case of errors.
return await new Promise(resolve => setTimeout(resolve, ms));
}
With this new delay function, you can implement your desired flow:
function first(){
console.log('first')
}
function second(){
console.log('second')
}
let run = async ()=>{
await delay(2000);
first();
await delay(2000)
second();
}
run();
setInterval doesn't play well with promises because it triggers a callback multiple times, while promise resolves once.
It seems that it's setTimeout that fits the case. It should be promisified in order to be used with async..await:
async () => {
await new Promise(resolve => setTimeout(() => resolve(first()), 2000));
await new Promise(resolve => setTimeout(() => resolve(second()), 2000));
}
await expression causes async to pause until a Promise is settled
so you can directly get the promise's result without await
for me, I want to initiate Http request every 1s
let intervalid
async function testFunction() {
intervalid = setInterval(() => {
// I use axios like: axios.get('/user?ID=12345').then
new Promise(function(resolve, reject){
resolve('something')
}).then(res => {
if (condition) {
// do something
} else {
clearInterval(intervalid)
}
})
}, 1000)
}
// you can use this function like
testFunction()
// or stop the setInterval in any place by
clearInterval(intervalid)
You could use an IFFE. This way you could escape the issue of myInterval not accepting Promise as a return type.
There are cases where you need setInterval, because you want to call some function unknown amount of times with some interval in between.
When I faced this problem this turned out to be the most straight-forward solution for me. I hope it help someone :)
For me the use case was that I wanted to send logs to CloudWatch but try not to face the Throttle exception for sending more than 5 logs per second. So I needed to keep my logs and send them as a batch in an interval of 1 second. The solution I'm posting here is what I ended up using.
async function myAsyncFunc(): Promise<string> {
return new Promise<string>((resolve) => {
resolve("hello world");
});
}
function myInterval(): void {
setInterval(() => {
void (async () => {
await myAsyncFunc();
})();
}, 5_000);
}
// then call like so
myInterval();
Looked through all the answers but still didn't find the correct one that would work exactly how the OP is asked. This is what I used for the same purpose:
async function waitInterval(callback, ms) {
return new Promise(resolve => {
let iteration = 0;
const interval = setInterval(async () => {
if (await callback(iteration, interval)) {
resolve();
clearInterval(interval);
}
iteration++;
}, ms);
});
}
function first(i) {
console.log(`first: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function second(i) {
console.log(`second: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
(async () => {
console.log('start');
await waitInterval(first, 1000);
await waitInterval(second, 1000);
console.log('finish');
})()
In my example, I also put interval iteration count and the timer itself, just in case the caller would need to do something with it. However, it's not necessary
In my case, I needed to iterate through a list of images, pausing in between each, and then a longer pause at the end before re-looping through.
I accomplished this by combining several techniques from above, calling my function recursively and awaiting a timeout.
If at any point another trigger changes my animationPaused:boolean, my recursive function will exit.
const loopThroughImages = async() => {
for (let i=0; i<numberOfImages; i++){
if (animationPaused) {
return;
}
this.updateImage(i);
await timeout(700);
}
await timeout(1000);
loopThroughImages();
}
loopThroughImages();
Async/await do not make the promises synchronous.
To my knowledge, it's just a different syntax for return Promise and .then().
Here i rewrote the async function and left both versions, so you can see what it really does and compare.
It's in fact a cascade of Promises.
// by the way no need for async there. the callback does not return a promise, so no need for await.
function waitInterval(callback, ms) {
return new Promise(resolve => {
let iteration = 0;
const interval = setInterval(async () => {
if (callback(iteration, interval)) {
resolve();
clearInterval(interval);
}
iteration++;
}, ms);
});
}
function first(i) {
console.log(`first: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function second(i) {
console.log(`second: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
// async function with async/await, this code ...
(async () => {
console.log('start');
await waitInterval(first, 1000);
await waitInterval(second, 1000);
console.log('finish');
})() //... returns a pending Promise and ...
console.log('i do not wait');
// ... is kinda identical to this code.
// still asynchronous but return Promise statements with then cascade.
(() => {
console.log('start again');
return waitInterval(first, 1000).then(() => {
return waitInterval(second, 1000).then(() => {
console.log('finish again');
});
});
})(); // returns a pending Promise...
console.log('i do not wait either');
You can see the two async functions both execute at the same time.
So using promises around intervals here is not very useful, as it's still just intervals, and promises changes nothing, and make things confusing...
As the code is calling callbacks repeatedly into an interval, this is, i think, a cleaner way:
function first(i) {
console.log(`first: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function second(i) {
console.log(`second: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function executeThroughTime(...callbacks){
console.log('start');
let callbackIndex = 0; // to track current callback.
let timerIndex = 0; // index given to callbacks
let interval = setInterval(() =>{
if (callbacks[callbackIndex](timerIndex++)){ // callback return true when it finishes.
timerIndex = 0; // resets for next callback
if (++callbackIndex>=callbacks.length){ // if no next callback finish.
clearInterval(interval);
console.log('finish');
}
}
},1000)
}
executeThroughTime(first,second);
console.log('and i still do not wait ;)');
Also, this solution execute a callback every secondes.
if the callbacks are async requests that takes more than one sec to resolve, and i can't afford for them to overlap, then, instead of doing iterative call with repetitive interval, i would get the request resolution to call the next request (through a timer if i don't want to harass the server).
Here the "recursive" task is called lTask, does pretty much the same as before, except that, as i do not have an interval anymore, i need a new timer each iteration.
// slow internet request simulation. with a Promise, could be a callback.
function simulateAsync1(i) {
console.log(`first pending: ${i}`);
return new Promise((resolve) =>{
setTimeout(() => resolve('got that first big data'), Math.floor(Math.random()*1000)+ 1000);//simulate request that last between 1 and 2 sec.
}).then((result) =>{
console.log(`first solved: ${i} ->`, result);
return i==2;
});
}
// slow internet request simulation. with a Promise, could be a callback.
function simulateAsync2(i) {
console.log(`second pending: ${i}`);
return new Promise((resolve) =>{
setTimeout(() => resolve('got that second big data'), Math.floor(Math.random()*1000) + 1000);//simulate request that last between 1 and 2 sec.
}).then((result) =>{ // promise is resolved
console.log(`second solved: ${i} ->`,result);
return i==4; // return a promise
});
}
function executeThroughTime(...asyncCallbacks){
console.log('start');
let callbackIndex = 0;
let timerIndex = 0;
let lPreviousTime = Date.now();
let lTask = () => { // timeout callback.
asyncCallbacks[callbackIndex](timerIndex++).then((result) => { // the setTimeout for the next task is set when the promise is solved.
console.log('result',result)
if (result) { // current callback is done.
timerIndex = 0;
if (++callbackIndex>=asyncCallbacks.length){//are all callbacks done ?
console.log('finish');
return;// its over
}
}
console.log('time elapsed since previous call',Date.now() - lPreviousTime);
lPreviousTime = Date.now();
//console.log('"wait" 1 sec (but not realy)');
setTimeout(lTask,1000);//redo task after 1 sec.
//console.log('i do not wait');
});
}
lTask();// no need to set a timer for first call.
}
executeThroughTime(simulateAsync1,simulateAsync2);
console.log('i do not wait');
Next step would be to empty a fifo with the interval, and fill it with web request promises...
Let’s say I want to resolve some promise every 2 seconds AFTER the function is run.
I tried a pattern like this at first: setInterval(MyAsyncFunction, 2000), but of course this does not wait for the promise to resolve before calling the function again.
I also tried setInterval(async () => await MyAsyncFunction(), 2000), but this is basically the same thing since I’m just wrapping it in another promise that will resolve while the setInterval is ticking down.
Is there a way to do what I am trying to do with setInterval or is there some alternative way to do this using setTimeout?
A Promise resolves once only, so therefore cannot, by definition, resolve periodically.
If you define a delay function, which returns a promise that resolves after timeout ms...
const delay = timeout => new Promise(resolve => setTimeout(resolve, timeout));
you could use it in an async loop:
async function foo(){
for(let i = 0; i < 100; ++i){
console.log("azrael");
await delay(2000);
}
}
If what you want is to call async function, then await for it, then wait extra fixed time, then repeat, you may approach it following ways.
You can call setTimeout from that function itself, so it will schedule its next run when it's finished:
async function myAsyncFunction() {
return new Promise(resolve => setTimeout(() => {
resolve('result'); // resolves, then
setTimeout(async () => { // schedules
let result = await myAsyncFunction(); // self-call
console.log('result') // and handles the result
}, 2000); // after extra 2s
}, 2000)); // imitates async function with 2s delay
}
async function main() {
let firstResult = await myAsyncFunction();
console.log(firstResult); // handles first call
}
More clean way is to wrap it in another async function with same setTimeout inside:
async function myAsyncFunction() {
// imitates async function
return new Promise(resolve => setTimeout(() => resolve('result'), 2000));
}
async function delayLoop() {
let result = await myAsyncFunction(); // waits for it
console.log(result);
setTimeout(delayLoop, 2000); // calls itself after extra 2s
}
delayLoop();
This way you do not have to modify original async function code and put callback right in there.
Both examples will await for async function for 2s before first result, and for 4s between calls - 2s async function delay + 2s extra delay after it.
I have an array-like structure that exposes async methods. The async method calls contain try-catch blocks which in turn expose more async methods in the case of caught errors. I'd like to understand why forEach doesn't play nicely with async/await.
let items = ['foo', 'bar', 'baz'];
// Desirable behavior
processForLoop(items);
/* Processing foo
* Resolved foo after 3 seconds.
* Processing bar
* Resolved bar after 3 seconds.
* Processing baz
* Resolved baz after 3 seconds.
*/
// Undesirable behavior
processForEach(items);
/* Processing foo
* Processing bar
* Processing baz
* Resolved foo after 3 seconds.
* Resolved bar after 3 seconds.
* Resolved baz after 3 seconds.
*/
async function processForLoop(items) {
for(let i = 0; i < items.length; i++) {
await tryToProcess(items[i]);
}
}
async function processForEach(items) {
items.forEach(await tryToProcess);
}
async function tryToProcess(item) {
try {
await process(item);
} catch(error) {
await resolveAfter3Seconds(item);
}
}
// Asynchronous method
// Automatic failure for the sake of argument
function process(item) {
console.log(`Processing ${item}`);
return new Promise((resolve, reject) =>
setTimeout(() => reject(Error('process error message')), 1)
);
}
// Asynchrounous method
function resolveAfter3Seconds(x) {
return new Promise(resolve => setTimeout(() => {
console.log(`Resolved ${x} after 3 seconds.`);
resolve(x);
}, 3000));
}
I'd like to understand why forEach doesn't play nicely with async/await.
It's easier when we consider that async is just syntactic sugar for a function returning a promise.
items.forEach(f) expects a function f as argument, which it executes on each item one at at time before it returns. It ignores the return value of f.
items.forEach(await tryToProcess) is nonsense equivalent to Promise.resolve(tryToProcess).then(ttp => items.forEach(ttp))
and functionally no different from items.forEach(tryToProcess).
Now tryToProcess returns a promise, but forEach ignores the return value, as we've mentioned, so it ignores that promise. This is bad news, and can lead to unhandled rejection errors, since all promise chains should be returned or terminated with catch to handle errors correctly.
This mistake is equivalent to forgetting await. Unfortunately, there's no array.forEachAwait().
items.map(f) is a little better, since it creates an array out of the return values from f, which in the case of tryToProcess would give us an array of promises. E.g. we could do this:
await Promise.all(items.map(tryToProcess));
...but all tryToProcess calls on each item would execute in parallel with each other.
Importantly, map runs them in parallel. Promise.all is just a means to wait for their completion.
As a rule...
I always use for of instead of forEach in async functions:
for (const item of items) {
await tryToProcess(item);
}
...even when there's no await in the loop, just in case I add one later, to avoid this foot-gun.
There is no way to use forEach with await like that - forEach cannot run asynchronous iterations in serial, only in parallel (and even then, map with Promise.all would be better). Instead, if you want to use array methods, use reduce and await the resolution of the previous iteration's Promise:
let items = ['foo', 'bar', 'baz'];
processForEach(items);
async function processForLoop(items) {
for (let i = 0; i < items.length; i++) {
await tryToProcess(items[i]);
}
}
async function processForEach(items) {
await items.reduce(async(lastPromise, item) => {
await lastPromise;
await tryToProcess(item);
}, Promise.resolve());
}
async function tryToProcess(item) {
try {
await process(item);
} catch (error) {
await resolveAfter3Seconds(item);
}
}
// Asynchronous method
// Automatic failure for the sake of argument
function process(item) {
console.log(`Processing ${item}`);
return new Promise((resolve, reject) =>
setTimeout(() => reject(Error('process error message')), 1)
);
}
// Asynchrounous method
function resolveAfter3Seconds(x) {
return new Promise(resolve => setTimeout(() => {
console.log(`Resolved ${x} after 3 seconds.`);
resolve(x);
}, 3000));
}
Also note that if the only await in a function is just before the function returns, you may as well just return the Promise itself, rather than have the function be async.