[重学前端基础] Promise的时序问题&JS执行机制

javascript/jquery

浏览数:31

2020-5-28

AD:资源代下载服务

前言

上一篇写了Promise的用法
真的掌握了Promise的用法吗?回顾一下Promise的特点,然后练习检测一下吧。

Promise的特点

  • Promise构造函数是同步的,promise.then()中的函数是异步的
  • Promise只能从pending->resolve或pending->reject,状态一但改变就不能再变
  • Promise.then、Promise.catch中不能return promise对象本身
  • Promise.then中必须是函数

Practice 1:

const promise3 = new Promise((r, j) => {
  r('success')
  j('error')
  r('success2')
})

promise3
  .then(res => {
    console.log('then:', res)
  })
  .catch(err => {
    console.log('error:', err)
  })
  
  //success

状态一旦改变就不能再变

Practice 2:

Promise.resolve(1)
  .then(res => {
    console.log(res)
    return 2
  })
  .catch(err => {
    console.log(err)
    return 3
  })
  .then(res => {
    console.log(res)
  })
  
  // 1
  // 2

链式调用,then()中return的结果正常情况下会传递给后面的then()中的回调函数

Promise.resolve(1)
  .then(res => {
    console.log(res)
    return new Error('error')
  })
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.log(('error', err))
    return 3
  })

return error对象时也不会被catch捕获,除非throw 一个error对象或者return Promise.reject()

new Promise((r, j) => {
  j('failed')
})
  .then(
    function success(res) {
      return res
    },
    function failed(err) {
      console.log('failed:', err)
    }
  )
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.log('catch:', err)
    return 3
  })
  
  //failed: failed

then(resolve, reject).catch()

catch是then的第二个参数的简写,reject捕获上一个函数的错误,这个时候catch捕获不到

new Promise((r, j) => {
  r('success')
})
  .then(
    function success(res) {
      throw new Error('error')
      return res
    },
    function failed(err) {
      console.log('failed:', err)
    }
  )
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.log('catch:', err)
    return 3
  })
  
  //catch: Error: error

如果是then中的第一个回调函数抛出错误,会被catch捕获

Practice 3:

new Promise((r, j)=> {
 console.log(1)
 r()
 console.log(2)
}).then(()=> {
 console.log(3)
})
console.log(4)

//1 2 4 3
const promise1 = new Promise((r, s) => {
  setTimeout(() => {
    console.log('success')
    r('success')
  }, 1000)
})

const promise2 = promise1.then(() => {
  throw new Error('error!!')
})

console.log('promise1:', promise1)
console.log('promise2:', promise2)

setTimeout(() => {
  console.log('promise1:', promise1)
  console.log('promise2:', promise2)
}, 2000)

做对了吗?如果错了,回顾下Js的执行机制吧。

回顾下Js的事件循环

Js执行机制是事件循环,Js是单线程的语言,执行多个任务时需要按顺序执行。Js的任务分为以下几类:

  • 广义上Js任务分为:同步任务和异步任务。
  • 狭义上Js任务分为:宏任务(setTimeout、setInterval、script)和微任务(promise.then、promise.nextTick)(任务指的是这些方法里面的回调函数)。**

当任务进入执行栈时,首先会判断任务是同步的还是异步的,同步的任务会被加入到主线程,异步的任务会被加入到event table,注册函数;当异步任务完成返回结果时,event table会将函数移入到event queue(任务队列),这个任务队列中包含了2种任务:微任务和宏任务。

当Js执行任务时,首先将主线程的任务执行完,然后再将任务队列中的任务加入到主线程中执行;这个过程中首先会将任务队列中的微任务加入到主线程中执行,执行完然后再取任务队列中的宏任务到主线程执行,执行完再进入下一个循环,依此类推…

process.nextTick(() => {
    console.log('nextTick')
})
new Promise((r, s) => {
    console.log('promise')
    r()
}).then(() => {
    console.log('then')
})

setImmediate(() => {
    console.log('immediate')
})
console.log('end')

promise
end
nextTick
then
immediate

最后检验自己是否掌握的一个例子:

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

在node中的结果:
1 7 6 8 2 4 9 11 3 10 5 12

console.log('1');

setTimeout(function() {
    console.log('2');
    
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})

new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
   
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

在浏览器中的结果:

1 7 8 2 4 5 9 11 12

做对了吗?

注意:

  • Node中的process.nextTick独立于事件循环之外, 优先级高于其他微任务如: then()。
  • 浏览器和 Node 环境下,microtask 任务队列的执行时机不同

    Node 端,microtask 在事件循环的各个阶段之间执行
    浏览器端,microtask 在事件循环的 macrotask 执行完之后执行

可以参考:浏览器与Node的事件循环(Event Loop)有何区别

最近更新:2020-03-27

作者:贝er