promise, async, await, execution order

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:

promise
  .then(() => {
    //
  })
  .then(() => {
    //
  })
  .then(() => {
    //
  })

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.

new Promise((resolve,reject)=>{
  resolve()
})

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

new Promise((resolve, reject) => {
  resolve(non-thenable)
})

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)

new Promise((resolve, reject) => {
  resolve(thenable)
})

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)
new Promise((resolve, reject) => {
  resolve(thenable)
})

is equivalent to

new Promise((resolve, reject) => {
  Promise.resolve().then(() => {
    thenable.then(resolve)
  })
})

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

let p1 = Promise.resolve(1)
Promise.resolve(p1).then(res => {
  console.log(res)
})
p1.then(res => {
  console.log(2)
})
//1
//2

while

let p1 = Promise.resolve(1)
new Promise((resolve, reject) => {
  resolve(p1)
}).then(res => {
  console.log(res)
})
p1.then(res => {
  console.log(2)
})
//2
//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:

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

And await can be used in async.

await

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

image

We can transform await code

const p1 = Promise.resolve(1)
const async1 = async function() {
  const res1 = await p1
  console.log(res1)
}
async1()
p1.then(() => console.log('after gen'))

to

const p1 = Promise.resolve(1)
const async1 = async function() {
  new Promise(resolve => {
    resolve(p1)
  }).then(res => {
    const res1 = res
    console.log(res1)
  })
}
async1()
p1.then(() => console.log('after gen'))

The result is the same on chrome 70.:

after gen
1

However, in chrome canary 73 the former result is

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:

await-spec-change

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

const p1 = Promise.resolve(1)
const async1 = async function() {
  Promise.resolve(p1).then(res => {
    const res1 = res
    console.log(res1)
  })
}
async1()
p1.then(() => console.log('after gen'))

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

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:

desugar-async

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

function spawn (genF, self) {
  return new Promise(function (resolve, reject) {
    var gen = genF.call(self)
    function step (nextF) {
      var next
      try {
        next = nextF()
      } catch (e) {
        // finished with failure, reject the promise
        reject(e)
        return
      }
      if (next.done) {
        // finished with success, resolve the promise
        resolve(next.value)
        return
      }
      // not finished, chain off the yielded promise and `step` again
      Promise.resolve(next.value).then(
        function (v) {
          step(function () {
            return gen.next(v)
          })
        },
        function (e) {
          step(function () {
            return gen.throw(e)
          })
        }
      )
    }
    step(function () {
      return gen.next(undefined)
    })
  })
}

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

function spawn (genF, self) {
  return new Promise(function (resolve, reject) {
    var gen = genF.call(self)
    function step (nextF) {
      var next
      try {
        next = nextF()
      } catch (e) {
        // finished with failure, reject the promise
        reject(e)
        return
      }
      if (next.done) {
        // finished with success, resolve the promise
        resolve(next.value)
        return
      }
      // not finished, chain off the yielded promise and `step` again
      /* modified line */
      new Promise(resolve => resolve(next.value)).then(
        /* origin line */
        // Promise.resolve(next.value).then(
        function (v) {
          step(function () {
            return gen.next(v)
          })
        },
        function (e) {
          step(function () {
            return gen.throw(e)
          })
        }
      )
    }
    step(function () {
      return gen.next(undefined)
    })
  })
}

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

const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
const async1 = async function () {
  const res1 = await p1
  console.log(res1)
  const res2 = await p2
  console.log(res2)
}
async1()
p1.then(() => console.log('after gen'))

with

const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
const gen = function* () {
  const res1 = yield p1
  console.log(res1)
  const res2 = yield p2
  console.log(res2)
}
const async1Eq = function () {
  spawn(gen, this)
}
async1Eq()
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.

Origin Post

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章