1

I'm interested to understand why using await immediately blocks, instead of lazily blocking only once the awaited value is referenced.

I hope this is an illuminating/important question, but I'm worried it may be considered out of scope for this site - if it is I'll apologize, have a little cry, and take the question elsewhere.

E.g. consider the following:

let delayedValue = (value, ms=1000) => {
  return new Promise(resolve => {
    setTimeout(() => resolve(val), ms);
  });
};

(async () => {
  let value = await delayedValue('val');
  console.log('After await');
})();

In the anonymous async function which immediately runs, we'll only see the console say After await after the delay. Why is this necessary? Considering that we don't need value to be resolved, why haven't the language designers decided to execute the console.log statement immediately in a case like this?

For example, it's unlike the following example where delaying console.log is obviously unavoidable (because the awaited value is referenced):

(async () => {
  let value = await delayedValue('val');
  console.log('After await: ' + value);
});

I see tons of advantages to lazy await blocking - it could lead to automatic parallelization of unrelated operations. E.g. if I want to read two files and then work with both of them, and I'm not being dilligent, I'll write the following:

(async() => {

  let file1 = await readFile('file1dir');
  let file2 = await readFile('file2dir');

  // ... do things with `file1` and `file2` ...

});

This will wait to have read the 1st file before beginning to read the 2nd. But really they could be read in parallel, and javascript ought to be able to detect this because file1 isn't referenced until later. When I was first learning async/await my initial expectation was that code like the above would result in parallel operations, and I was a bit disappointed when that turned out to be false.

Getting the two files to read in parallel is STILL a bit messy, even in this beautiful world of ES7 because await blocks immediately instead of lazily. You need to do something like the following (which is certainly messier than the above):

(async() => {

  let [ read1, read2] = [ readFile('file1dir'), readFile('file2dir') ];
  let [ file1, file2 ] = [ await read1, await read2];

  // ... do things with `file1` and `file2` ...

});

Why have the language designers chosen to have await block immediately instead of lazily?

E.g. Could it lead to debugging difficulties? Would it be too difficult to integrate lazy awaits into javascript? Are there situations I haven't thought of where lazy awaits lead to messier code, poorer performance, or other negative consequences?

Gershom Maes
  • 7,358
  • 2
  • 35
  • 55
  • 4
    What about `await` expressions which are never assigned to a variable, which cause a *side effect* which in turn is then needed in the next line of code? How would lazy `await` work in those cases? – nicholaswmin Apr 30 '18 at 15:46
  • 2
    You can already do everything you are asking for by just using Promises instead of `await`. The whole point of `await` is that you get the behavior you are unhappy with. – Mark Apr 30 '18 at 15:47
  • lazily block? What is this supposed to mean? – Deblaton Jean-Philippe Apr 30 '18 at 15:48
  • 1
    Async operations can (and commonly do) have numerous side-effects, such as addressing a frontend data store, showing-hiding loading indicators and more, making the execution order non-deterministic would make using `async` a race condition hell. Sure, you could restructure your code to work around it, but at the same time, you can also restructure your code _now_, without bringing in hidden race conditions. – Etheryte Apr 30 '18 at 15:49
  • Ahh I hadn't thought about side-effects! – Gershom Maes May 01 '18 at 21:01

1 Answers1

2

Reason 1: JavaScript is not lazily evaluated. There is no infrastructure to detect "when a value is actually needed".

Reason 2: Implicit parallelism would be very hard to control. What if I wanted my files to be read sequentially, how would I write that? Changing little things in the syntax should not lead to very different evaluation.

Getting the two files to read in parallel is STILL a bit messy

Not at all:

const [file1, file2] = await Promise.all([readFile('file1dir'), readFile('file2dir')]);

You need to do something like the following

No, you absolutely shouldn't write it like that.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375