资讯专栏INFORMATION COLUMN

promise, async, await, execution order

neuSnail / 2895人阅读

摘要:

async can be transformed to promise. So, if we want to understand async, we have to understand promise first.

Promise

Normally, promise is easy to understand, especially when using like this:

</>复制代码

  1. promise
  2. .then(() => {
  3. //
  4. })
  5. .then(() => {
  6. //
  7. })
  8. .then(() => {
  9. //
  10. })

then after then would make the order of async callbacks clear. Actually we shouldn"t rely on async callbacks order. But a certain execution order would make me feel more comfortable. However, sometimes, things are different.

RESOLVE and Promise.resolve()

Normally, I initialize a promise by Promise.resolve() because it seems too troublesome to use Promise constructor like below which I called it RESOLVE in this article.

</>复制代码

  1. new Promise((resolve,reject)=>{
  2. resolve()
  3. })

Also, I would use promise in this article for reference to thenable. Because in most cases, we uses promise and promise is the subset of thenable.

Normally, I used it by Promise.resolve(non-thenable) which is equivalent to RESOLVE(non-thenable)

</>复制代码

  1. new Promise((resolve, reject) => {
  2. resolve(non-thenable)
  3. })

So, it doesn"t matter which one you choose. RESOLVE(non-thenable) or Promise.resolve(non-thenable).

However, when it comes to thenable, things are different. Promise.resolve(thenable) is not equivalent to RESOLVE(thenable)

</>复制代码

  1. new Promise((resolve, reject) => {
  2. resolve(thenable)
  3. })

I explained it carefully in What"s the difference between resolve(promise) and resolve("non-thenable-object")?. And here is the conclusion:

for non-thenable, Promise.resolve(non-thenable) is equivalent to RESOLVE(non-thenable)

for thenable, Promise.resolve(thenable) is not equivalent to RESOLVE(thenable) because RESOLVE(thenable)

</>复制代码

  1. new Promise((resolve, reject) => {
  2. resolve(thenable)
  3. })

is equivalent to

</>复制代码

  1. new Promise((resolve, reject) => {
  2. Promise.resolve().then(() => {
  3. thenable.then(resolve)
  4. })
  5. })

according to spec. It"s obviously not equivalent to Promise.resolve(thenable). You can test it by this example:

</>复制代码

  1. let p1 = Promise.resolve(1)
  2. Promise.resolve(p1).then(res => {
  3. console.log(res)
  4. })
  5. p1.then(res => {
  6. console.log(2)
  7. })
  8. //1
  9. //2

while

</>复制代码

  1. let p1 = Promise.resolve(1)
  2. new Promise((resolve, reject) => {
  3. resolve(p1)
  4. }).then(res => {
  5. console.log(res)
  6. })
  7. p1.then(res => {
  8. console.log(2)
  9. })
  10. //2
  11. //1

So, here comes another question. When would we use Promise.resolve(thenable) or RESOLVE(thenable)? It doesn"t seem to be that common.

Yes, indeed. Except async and await.

async and await

As we all know or spec says that the result of async returns promise. For example:

</>复制代码

  1. (async function(){}()).toString() //"[object Promise]"

And await can be used in async.

await

So, how does await work in async? According to spec:Await:

We can transform await code

</>复制代码

  1. const p1 = Promise.resolve(1)
  2. const async1 = async function() {
  3. const res1 = await p1
  4. console.log(res1)
  5. }
  6. async1()
  7. p1.then(() => console.log("after gen"))

to

</>复制代码

  1. const p1 = Promise.resolve(1)
  2. const async1 = async function() {
  3. new Promise(resolve => {
  4. resolve(p1)
  5. }).then(res => {
  6. const res1 = res
  7. console.log(res1)
  8. })
  9. }
  10. async1()
  11. p1.then(() => console.log("after gen"))

The result is the same on chrome 70:

</>复制代码

  1. after gen
    1

However, in chrome canary 73 the former result is

</>复制代码

  1. 1
    after gen

Why?

The reason can be found in https://github.com/tc39/ecma2... Simply say, the spec to await was going to change to:

What"s difference?

The difference is exactly the difference between RESOLVE(thenable) and Promise.resolve(thenable).

In the past and chrome 70, we are using RESOLVE(thenable), so I transformed the code like above. If using this new spec, the code should be transformed to

</>复制代码

  1. const p1 = Promise.resolve(1)
  2. const async1 = async function() {
  3. Promise.resolve(p1).then(res => {
  4. const res1 = res
  5. console.log(res1)
  6. })
  7. }
  8. async1()
  9. p1.then(() => console.log("after gen"))

In this case, chrome 70 and canary 73 would all get

</>复制代码

  1. 1
    after gen

That"s the spec change for await. Hope you both understand the way before and after change.

async

Now, let"s talk about async. How does async work? According to spec:

</>复制代码

  1. The spawn used in the above desugaring is a call to the following algorithm. This algorithm does not need to be exposed directly as an API to user code, it is part of the semantics of async functions.

And the spawn is

</>复制代码

  1. function spawn (genF, self) {
  2. return new Promise(function (resolve, reject) {
  3. var gen = genF.call(self)
  4. function step (nextF) {
  5. var next
  6. try {
  7. next = nextF()
  8. } catch (e) {
  9. // finished with failure, reject the promise
  10. reject(e)
  11. return
  12. }
  13. if (next.done) {
  14. // finished with success, resolve the promise
  15. resolve(next.value)
  16. return
  17. }
  18. // not finished, chain off the yielded promise and `step` again
  19. Promise.resolve(next.value).then(
  20. function (v) {
  21. step(function () {
  22. return gen.next(v)
  23. })
  24. },
  25. function (e) {
  26. step(function () {
  27. return gen.throw(e)
  28. })
  29. }
  30. )
  31. }
  32. step(function () {
  33. return gen.next(undefined)
  34. })
  35. })
  36. }

However, I think the spawn is the future version which doesn"t apply to chrome 70 because it used Promise.resolve(next.value) instead of RESOLVE(next.value) to transform await. So, I thought the old version or version applied to chrome 70 should be

</>复制代码

  1. function spawn (genF, self) {
  2. return new Promise(function (resolve, reject) {
  3. var gen = genF.call(self)
  4. function step (nextF) {
  5. var next
  6. try {
  7. next = nextF()
  8. } catch (e) {
  9. // finished with failure, reject the promise
  10. reject(e)
  11. return
  12. }
  13. if (next.done) {
  14. // finished with success, resolve the promise
  15. resolve(next.value)
  16. return
  17. }
  18. // not finished, chain off the yielded promise and `step` again
  19. /* modified line */
  20. new Promise(resolve => resolve(next.value)).then(
  21. /* origin line */
  22. // Promise.resolve(next.value).then(
  23. function (v) {
  24. step(function () {
  25. return gen.next(v)
  26. })
  27. },
  28. function (e) {
  29. step(function () {
  30. return gen.throw(e)
  31. })
  32. }
  33. )
  34. }
  35. step(function () {
  36. return gen.next(undefined)
  37. })
  38. })
  39. }

You can tested it by comparing the result of below example.

</>复制代码

  1. const p1 = Promise.resolve(1)
  2. const async1 = async function () {
  3. const res1 = await p1
  4. console.log(res1)
  5. }
  6. async1()
  7. p1.then(() => console.log("after gen"))

with

</>复制代码

  1. const p1 = Promise.resolve(1)
  2. const gen = function* () {
  3. const res1 = yield p1
  4. console.log(res1)
  5. }
  6. const async1Eq = function () {
  7. spawn(gen, this)
  8. }
  9. async1Eq()
  10. p1.then(() => console.log("after gen"))

The result would be:

On chrome 70, with the former spawn, you will get the different result. While you will get the same result with the latter spawn.

In the same way, on chrome 73, with the former spawn, you will get the same result. While you will get the different result with the latter spawn.

Problems in async

As async return value is using a RESOLVE, so it might be a little delay when return promise in async function body. For example:

</>复制代码

  1. const p1 = Promise.resolve(1)
  2. const async1 = async () => {
  3. return p1
  4. }
  5. async1().then(res => {
  6. console.log(res)
  7. })
  8. p1.then(res => {
  9. console.log(2)
  10. }).then(res => {
  11. console.log(3)
  12. })

chrome 70 ,73 all returns

</>复制代码

  1. 2
    3
    1

That"s correct. Because spec: Runtime Semantics: EvaluateBody uses RESOLVE in async implementation.

So, why not using Promise.resolve() in async implementation like await? @MayaLekova explained in https://github.com/tc39/ecma2... ,

</>复制代码

  1. Deferring the implicit creation of the wrapper promise inside async functions in case we actually need to await on an asynchronous task (which excludes async functions without await or with awaits only on resolved promises) will indeed remove the performance overhead of turning a synchronous function to asynchronous. It will though introduce the possibility to create starvation, for instance if the await statement is in a loop. This possibility outweighs the performance gain IMO, so it"s better not to do it.

    Second, if we want to remove the extra tick for chaining native, non-patched promises (introduce "eager" chaining), this will effectively change observable behaviour for applications already shipping with native promises. Think of it as changing the behaviour of line 2 from above to be equivalent to line 3, which will actually introduce further inconsistency.

So, in current situation, we can only transform code above by RESOLVE.

</>复制代码

  1. const p1 = Promise.resolve(1)
  2. const async1 = () => {
  3. return new Promise(resolve => {
  4. resolve(p1)
  5. })
  6. }
  7. async1().then(res => {
  8. console.log(res)
  9. })
  10. p1.then(res => {
  11. console.log(2)
  12. }).then(res => {
  13. console.log(3)
  14. })

which is equivalent to

</>复制代码

  1. const p1 = Promise.resolve(1)
  2. const async1 = () => {
  3. return new Promise(resolve => {
  4. return Promise.resolve().then(() => {
  5. p1.then(resolve)
  6. })
  7. })
  8. }
  9. async1().then(res => {
  10. console.log(res)
  11. })
  12. p1.then(res => {
  13. console.log(2)
  14. }).then(res => {
  15. console.log(3)
  16. })

So, if implementation or spec of async doesn"t change, we may need to avoid return promise in async expect you really know what"s happening.

In the case above, if you really want to avoid the delay, you should avoid using async. You can do it by:

</>复制代码

  1. const p1 = Promise.resolve(1)
  2. const async1 = () => {
  3. return p1
  4. }
  5. async1().then(res => {
  6. console.log(res)
  7. })
  8. p1.then(res => {
  9. console.log(2)
  10. }).then(res => {
  11. console.log(3)
  12. })

which can be written to

</>复制代码

  1. const p1 = Promise.resolve(1)
  2. p1.then(res => {
  3. console.log(res)
  4. })
  5. p1.then(res => {
  6. console.log(2)
  7. }).then(res => {
  8. console.log(3)
  9. })

That"s why I love promise instead of async.

Conclusion

I hope you can understand async, await and promise execution order now. When talking about these words, the most important thing is to figure out which resolve it should use.

RESOLVE or Promise.resolve()? RESOLVE(thenable) is different from Promise.resolve(thenable)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100484.html

相关文章

  • 现代JS中的流程控制:详解Callbacks 、Promises 、Async/Await

    摘要:控制台将显示回调地狱通常,回调只能由一个异步函数调用。更多资源使更友好规范使用异步函数简化异步编码旅程异步编程是一项在中无法避免的挑战。 JavaScript经常声称是_异步_。那是什么意思?它如何影响发展?近年来这种方法有何变化? 请思考以下代码: result1 = doSomething1(); result2 = doSomething2(result1); 大多数语言都处理每...

    shadowbook 评论0 收藏0
  • 现代JS中的流程控制:详解Callbacks 、Promises 、Async/Await

    摘要:控制台将显示回调地狱通常,回调只能由一个异步函数调用。更多资源使更友好规范使用异步函数简化异步编码旅程异步编程是一项在中无法避免的挑战。 JavaScript经常声称是_异步_。那是什么意思?它如何影响发展?近年来这种方法有何变化? 请思考以下代码: result1 = doSomething1(); result2 = doSomething2(result1); 大多数语言都处理每...

    oujie 评论0 收藏0
  • 现代JS中的流程控制:详解Callbacks 、Promises 、Async/Await

    摘要:控制台将显示回调地狱通常,回调只能由一个异步函数调用。更多资源使更友好规范使用异步函数简化异步编码旅程异步编程是一项在中无法避免的挑战。 JavaScript经常声称是_异步_。那是什么意思?它如何影响发展?近年来这种方法有何变化? 请思考以下代码: result1 = doSomething1(); result2 = doSomething2(result1); 大多数语言都处理每...

    anquan 评论0 收藏0
  • [译]理解 Node.js 事件驱动机制

    摘要:事件驱动机制的最简单形式,是在中十分流行的回调函数,例如。在回调函数这种形式中,事件每被触发一次,回调就会被触发一次。回调函数需要作为宿主函数的一个参数进行传递多个宿主回调进行嵌套就形成了回调地狱,而且错误和成功都只能在其中进行处理。 学习 Node.js 一定要理解的内容之一,文中主要涉及到了 EventEmitter 的使用和一些异步情况的处理,比较偏基础,值得一读。 阅读原文 大...

    Snailclimb 评论0 收藏0
  • 【译】理解Node事件驱动架构

    摘要:回调方式将回调函数作为参数传递给主函数,同时在主函数内部处理错误信息。模块是促进中对象之间交流的模块,它是异步事件驱动机制的核心。在异步函数的回调中,根据执行情况触发或者事件。比如,当异常事件触发关闭数据库的动作时。 原文链接:Understanding Nodejs Event-driven Architecture 作者:Samer Buna 翻译:野草 本文首发于前端早读课【...

    mrcode 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<