简体   繁体   中英

Promises vs async await behaviour javascript. Concurrency mode

i'm trying to avoid concurrency of a async function in order to wait for the first call of this function to be finished before i can call it again the same function:

 const disallowConcurrency = (fn) => { let inprogressPromise = Promise.resolve(); return async(...args) => { await inprogressPromise; inprogressPromise = inprogressPromise.then(() => fn(...args)); }; }; const initCmp = async(arg) => { return new Promise((res) => { console.log('executed'); setTimeout(() => res(console.log(arg)), 1000); }); }; const cmpConcurrentFunction = disallowConcurrency(initCmp); cmpConcurrentFunction('I am called 1 second later'); cmpConcurrentFunction('I am called 2 seconds later');

So here i'm creating a closure with a promise as a value passed to the inner function. The return function will wait for the previous call ( in the first run it's a resolved Promise ) and the will assign to inprogressPromise the promise returned by the then which in this case will be a new Promise returned by the initCmp function. So next time we call the function it will wait for the previous one to be done. At least this is how i'm understanding it. Did i get it right?

But i don't understand why this keeps working if i remove the await inprogressPromise for example like this:

    const disallowConcurrency = (fn) => {
  let inprogressPromise = Promise.resolve();

  return async (...args) => {
    inprogressPromise = inprogressPromise.then(() => fn(...args));
  };
};

So the await is not necessary? Why?

Further more i was expecting this to work:

 const disallowConcurrency = (fn) => {
  let inprogressPromise = Promise.resolve();

  return async (...args) => {
    await inprogressPromise;
    inprogressPromise = fn(...args);
  };
};

Because i was thinking that first i'm awaiting for the previous promise to be done and then i would call and assign the returned promise on the inprogressPromise .

But it's not working and the two functions are called at the same time.

Can someone clarify me what's going on here?

If you try the code you will see that the second call

cmpConcurrentFunction("I am called 2 seconds later")

will wait for the first promise to be done.

Basically i have written a node package that gets imported into the browser in a web app through npm. This node package library has an init function that has some async code and can be called multiple time in different part of the code. If is called a second time for example i want to be sure that the first execution will be done before trying to execute the code again.

But my point is trying to understand why that code works and why it doesn't if i use the last version

Thanks!

To answer your questions, let me first try to explain how your code is working.

Understanding how your code works

Following steps explain the execution of your code:

  1. Script execution start

  2. Call disallowConcurrency function, passing in the initCmp as an argument. cmpConcurrentFunction is assigned the return value of disallowConcurrency function

  3. Call cmpConcurrentFunction for the first time, passing in 'I am called 1 second later' as an argument. During this invocation, inprogressPromise is a resolved promise returned by Promise.resolve() . Awaiting it pauses the function execution.

  4. Call cmpConcurrentFunction for the second time, passing in 'I am called 2 seconds later' as an argument. During this second invocation, inprogressPromise is still a resolved promise returned by Promise.resolve() . Awaiting it pauses the function execution.

  5. Synchronous execution of script ends here. Event loop can now start processing the micro-task queue

  6. Function paused as a result of first invocation of cmpConcurrentFunction is resumed, calling then() method on the promise returned by Promise.resolve() . The value of the inprogressPromise is updated by assigning it a new promise returned by inprogressPromise.then(...)

  7. Function paused as a result of second invocation of cmpConcurrentFunction is resumed. From step 6, we know that inprogressPromise is now a promise returned by inprogressPromise.then(...) . So, calling then() on it, we are simply creating a promise chain, adding a new then() method call at the end of the promise chain created in step 6.

    At this point, we have a chain that looks like this:

     inProgressPromise.then(() => fn('I am called 1 second later')).then(() => fn('I am called 2 seconds later'))
  8. Callback function of the first then method is called, which in turn calls the fn argument, which is initCmp function. Calling initCmp sets up a timer that resolves the promise initCmp returns after 1 second. When the promise is resolved after 1 second, 'I am called 1 second later' is logged on the console.

  9. When the promise returned by the first call to initComp function in the callback function of first then method, is resolved, it resolves the promise returned by the first then method. This leads to the invocation of the callback function of the second then method. This again calls the initComp function, which then returns a new promise that is resolved after another 1 second.

This explains why you see 'I am called 2 seconds later' logged on the console after 2 seconds.


Now answering your questions:

But i don't understand why this keeps working if i remove the await inprogressPromise

await inprogressPromise serves no purpose in your code other than pausing the calls to cmpConcurrentFunction function. Both calls await the same promise that is returned by Promise.resolve() .

So the await is not necessary? Why?

Because the output you see on the console is not because of await , but because of the promise chain (step 7 above) that is constructed as a result of two invocations of the cmpConcurrentFunction function.

Further more i was expecting this to work:

 const disallowConcurrency = (fn) => { let inprogressPromise = Promise.resolve(); return async (...args) => { await inprogressPromise; inprogressPromise = fn(...args); }; };

The above code doesn't works as your original code because now the promise chain isn't constructed which is key to the output your original code produces.

With this implementation of disallowConcurrency , your code is executed as explained below:

  1. Call cmpConcurrentFunction function, passing in 'I am called 1 second later' as an argument

  2. Awaiting inprogressPromise pauses the function execution

  3. Call cmpConcurrentFunction for the second time, passing in 'I am called 2 seconds later' as an argument

  4. Awaiting inprogressPromise pauses the function execution. At this point, both invocations of cmpConcurrentFunction are paused and both function invocations are awaiting the same promise that was created as a result of calling Promise.resolve() .

    This is the key point here: both invocations of cmpConcurrentFunction function are awaiting the same promise that was created by Promise.resolve() . Why are both invocations awaiting the same promise? Because the cmpConcurrentFunction is invoked for the second time before the first invocation of cmpConcurrentFunction is resumed and calls the initComp function

  5. Resume the first invocation of cmpConcurrentFunction , call the initComp function and assign its return value to inprogressPromise .

    Calling initComp function sets up a timer that resolves the promise initComp function returns after 1 second

  6. Resume the second invocation of cmpConcurrentFunction , call the initComp function and assign its return value to inprogressPromise , overwriting the current value of inprogressPromise which is the promise returned by the first call to initComp function.

    Calling initComp function sets up a timer that resolves the promise initComp function returns after 1 second

At this point, initComp function has been invoked twice, setting up two separate timers that resolve their respective promise after 1 second each.

To summarize the differences between the original implementation of disallowConcurrency function and this one is that in the original implementation, you have a promise chain which resolves the promises sequentially whereas in the second implementation, you have two separate promises (that don't depend on each other) and they are resolved after 1 second each.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM