What's the usage of 'await 0'?
Asked Answered
F

2

7

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.

Fellatio answered 22/12, 2018 at 13:37 Comment(6)
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.Edson
As @Edson 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.Winnah
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: typescriptlang.org/play/… . 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)Edson
@Edson 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?Fellatio
@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.Edson
@Edson 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'.Fellatio
F
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.

Fellatio answered 1/5, 2020 at 3:28 Comment(2)
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: developer.mozilla.org/en-US/docs/Web/API/…Edson
@Edson 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.Fellatio
E
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.

Enjoy answered 22/12, 2018 at 13:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.