6

Summary

I'm looking at TODO MVC Example with shadow DOM and customElements and in litRender.js there's a weird code in invalidate() function:'await 0'. I want to know what's the purpose of this code.

Background

I did a little search on Google but I couldn't find any case like that. I'm very new to javascript and Webpack so I have no idea how to debug the application(I tried to re-bundle it with --devtool option, but I got errors).

The author's explanation is(sorry for translation):

litRender.js can be found under src/libs and helps render each component of this application. Each component uses a mix of litRender in the form of class SomeComponent extends LitRender (HTMLElement). If the content is updated several times a time code is intended to help improve performance by not rendering every time, it collected the rendering time. Calling this.invalidate on a component that extends it will reserve a call to the render function defined in the component.

As the author mentioned above, invalidate() is used to render shadow DOM. Here's how the author uses it.

Main question

I want to know what does 'await 0' in litRender.js really does.

ditto
  • 93
  • 9
  • 2
    This adds a delay, if I'm not wrong, so I'm really confident this is performance-related. `await 0` will force the above code to wait for the promise to resolve in order to execute the next code. Promises implementations introduces delays by themselves, hence this will slightly slow down the amount of executions of that function call specifically, increasing the overall performance. As you can see, in fact, this is called in the `if (!instant)`, otherwise the await will be ignored. – briosheje Dec 22 '18 at 13:40
  • 1
    As @briosheje pointed out, intent of this code to make execution async. Similar to wrap a function execution into `setTimeout` function with timeout of `0`, or into `Promise.resolve().then()` method. – HynekS Dec 22 '18 at 13:47
  • 1
    As a side note, which **might** help you understand (or may confuse you even more), you can try the typescript playground and see the compiled code of an async function: http://www.typescriptlang.org/play/#src=async%20function%20b(a%3A%20boolean)%20%7B%0D%0A%20%20%20%20return%20a%20%3F%20await%200%20%3A%200%3B%0D%0A%7D . It's not that easy to understand, but you may notice that in case `await 0` is returned, the execution will take more time (look at the b() compiled function on the right textarea of the page) – briosheje Dec 22 '18 at 14:02
  • @briosheje Thanks for helping me. As far as I understand, `invalidate()` will prevent rendering shadow DOM simultaneously. It will try to do the work not at the same time(if it's possible). Am I wrong? – ditto Dec 22 '18 at 15:39
  • 1
    @kimjs3550 I think it will just delay the execution of the render function. I'm not sure whether this will prevent from rendering shadow DOM elements simultaneuosly, but it surely will reduce the call stack. I would keep this question opened for more expert people, I can't really foresee many advantages of using that system, it reminds me the angular's old "digest" cycle, and I'm not a big fan of it... But still, someone can surely provide a better explanation. From a pure javascript perspective, though, I can see that it's just a clever sort of delay. – briosheje Dec 22 '18 at 16:14
  • @briosheje I finally found the answer. Please read my answer. I think all of your explanation was correct except you omitted the term 'event loop' or 'task'. – ditto May 01 '20 at 03:31

2 Answers2

3

After one year I finally found the answer: await 0 was used in order to free event loop so browser can draw frame.

Note: My explanation is verbose and has some poor grammer usages. It may be better to read MDN doc linked above.

Since JS is designed to be a single-threaded(while there exists a web worker) languaged, it has a concurrency model called event loop. Consider a following situation:

console.log('before timeout')
setTimeout(() => console.log('inside timeout'), 0)
console.log('after timeout')

Result will be:

before timeout
after timeout
inside timeout

It can be confusing at first. You might think: The timeout was set with delay of 0, thereby it would execute its callback before executing next line of code! But that's not true.

To understand the above code, let's first see how JS handle code execution. It has a storage called stack that tracks where the current function execution was originated from.

function a() {
  console.log('a()')
}

a()
console.log('end')

In the above code, code outside brackets are executed first. Then, function a will be executed. At this point, the stack will look like this:

    a
(Main) -> code outside every brackets

If some function calls another function the called one gets stacked on the previous one. Now the function the end and JS clears it from the top of the stack, go back to previous position, execute remaining code until it reaches the end, and so on. By utilizing stack JS can figure out where to go after current function ends.

Then what happens when we execute the setTimeout? The important thing here is that setTimeout is not a language feature but a platform feature which browser deals with. The browser waits for the given time while continuing following code execution. Then the timeout ends and the browser needs to execute its callback, but the problem is that the another piece of code might still on execution. To solve this, the browser makes callback as a task and register it in task queue. Tasks in it will be executed in sequence when the stack is empty.

This explains the odd behavior of the first code snippet: setTimeout's callback gets registered as task and it waits until stack is empty. After logging the second message main code execution ends and callback finally gets executes.

Promise is handled in similar way(but as microtask). Whether or not the right side of await is a promise, every codes that needs to be exexuted after await gets registered as microtask. The main benefit of doing so is related to the fact that browser only draw frame when the stack empty. By registering remaining code as microtask stack gets empty before the microtask gets executed, therefore browser can draw frame in that timing.

ditto
  • 93
  • 9
  • `It can be confusing at first. The timeout was set with delay of 0, thereby it would execute its callback before executing next line of code!` This is not true. `setTimeout` with omitted delay argument or 0 does not IMMEDIATELY execute the function, as said in MDN "If this argument is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, the next event cycle. Note that in either case, the actual delay may be longer than intended;". Check this: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout – briosheje May 02 '20 at 08:14
  • @briosheje oh, it is just a piece of my thinking when I first contacted the concept 'event loop'; I added it because some reader might think just like me at first. I'll edit that sentence to clarify that it's a misunderstanding, not fact. – ditto May 02 '20 at 17:58
2

awaitstatament must be used into a async code so as the docs say here into the description section, await will be used to pause the execution of the function until the promise is resolved or rejected, so if the statement next to await is not a promise, so JS will consider as a resolved promise.

Hope it can help you understand.

Juorder Gonzalez
  • 1,642
  • 1
  • 8
  • 10