javascript的同步异步编程

js中的同步和异步

  • 同步
    js是单线程的,浏览器只会分配一个js引擎线程,用来执行js代码,当其执行代码时,js一次只能执行一次事件,这就是js中的同步

  • 异步
    异步是由浏览器任务队列的机制决定的:
    我们说js单线程指的是浏览器分配给js执行时的js引擎线程是单线程的,一般也称为主线程,但浏览器本身是多进程的,其主要的进程是渲染进程,一个tab就是一个渲染进程,所以一个tab崩溃才不会影响别的tab。一个渲染进程会包括定时器进程、事件处理线程、js引擎线程等。
    js引擎线程遇到要异步执行的任务(比如定时器、事件绑定、AjaxPromiseasync await等),浏览器渲染进程会开启对应的线程去处理异步任务,当任务执行完成后会返回一个回调任务,这个回调任务就会存放到对应的任务队列event queue中,等待被js主线程同步调用;

    当浏览器同步任务都执行完后,浏览器渲染线程闲下来了,就会去任务队列按照指定的顺序领一个任务执行,当执行完后,会按照顺序领下一个任务,直到任务队列清空为止,这个过程,就是我们常说的事件循环event loop

    任务队列event queue中存放有两种:

    • 宏任务:定时器(即使设为0,也是4ms后执行代码),简单的可以记为除以下微任务的都是宏任务
    • 微任务:promise、 async await、promise.nextTice
      微任务的优先级高于宏任务

常见的异步任务

Promise

其实Promise本身并不是异步执行的,当new Promise((resolve, reject) => {}),这个里面的函数(resolve, reject) => {}是立即执行的,它的异步体现在resolve()reject()回调,不是立即通知then中的方法执行,而是等其处理完事情后,再把promise的状态改变,并通知then中的方法执行

new Promise((resolve, reject) => {
  //  这里立即执行
  // ...
  resolve()
}).then(resolve => {
  
}, reason => {
})

generator

generator可以通过yield将函数的执行权交出去,然后通过调用next()方法执行一次回调

function* gen() {
  let a = yield 111;
  console.log(a)
  let b = yield 222;
  console.log(b)
  let c = yield 333;
  console.log(c)
}
let t = gen()
t.next(1)  // 第一次执行,传递的参数无效,故无打印结果
t.next(2)  // a输出2
t.next(3)  // b输出3
t.next(4)  // c输出4

async await

async awaitgenerator(本质上是Promise)的语法糖,async对应的是generator中的*号;await对应的是yield,可以“暂停”异步方法的执行,直到拿到异步执行结果后,再以同步方式执行后面的代码。

async await也是异步编辑的终极方案,以同步的方式写异步。

下面例子中,async函数,也不是func函数本身是异步的,它会立即执行await对应的表达式,即函数func1(),然后看它的结果,await必须保证返回的是成功态,才会把下面代码执行,所以它的异步体现在:await下面的代码先不执行,等func1()返回成功才执行

async function func() {
  // func1立即执行,但console.log(1111)要等func1的结果才能执行
  await func1();
  console.log(1111)
}

// 相当于
function func() {
  // func1立即执行,但console.log(1111)要等func1的结果才能执行
   new Promise(resovle => {
     func1()
     resolve()
   }).then(res => {
    console.log(1111)
  })
}

关于异常
一般我们执行promise的话,使用try-catch是无法获取到异步代码抛出的异常的,一般需要在promisecatch中获取
但是使用await,就可以使用同步方式try-catch来获取错误了

(async function () {
  try {
    await interview(1)
    await interview(2)
    await interview(3)
    console.log('smile');
  }catch (e) {
    return console.log(`cry at ${e}`)
  }
})()

function interview(round) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(Math.random() > 0.5) {
        resolve(round)
        console.log('成功了');
      }else {
        reject(round)
      }
      
    }, 300)
  })
}

上题目

说那么多,不如做题来理解,这道题整合了async/await、Promise、setTimeout、script几种类型

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}


async function async2() {
  console.log('async2')
}

console.log('script start')

setTimeout(function() {
  console.log('setTimeout');
}, 0)

async1()

new Promise(function(resolve) {
  console.log('promise1')
  resolve()
}).then(function() {
  console.log('promise2');
}).then(function() {
  console.log('promise3');
})


console.log('script end')

// chrome  89.0.4389.90(正式版本)输出结果如下,按微任务放置顺序执行:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// promise3
// setTimeout


// nodejs v10.15.3输出结果如下,会优先执行promise,再执行await后语句: 
// script start
// async1 start
// async2
// promise1
// script end
// promise2
// promise3
// async1 end
// setTimeout

分析:

  1. async1,async2方法定义,先略过不管,执行console.log('script start') 输出script start
  2. setTimeout设置间隔为0,但是依然需要至少等4ms后才执行,然后放到宏任务队列,等待被执行
  3. async1()方法执行,输出async1 start
  4. await async2()这句立即执行async2(),输出async2
  5. console.log('async1 end')因为await async2,相当于Promise.resolve().then(res => console.log('async1 end')),所以被放到微任务队列
  6. new Promise立即执行console.log('promise1'),输出promise1resolve()后,then中的方法放入微任务队列
  7. 同步代码执行console.log('script end'),输出script end
  8. 这一轮后的结果:
    宏任务:setTimeout
    微任务:1. await async2后面的代码,2. promise2
  9. 这时候浏览器渲染线程空闲了,去任务队列中找任务,这时候就涉及到任务队列的优先级了,微任务先于宏任务这个顺序是必须的,但微任务队列的优先级却是不一定的
    一般来说,正常微任务执行顺序,是按谁先放置的谁就先执行,但是不同的v8版本或引擎版本对于它的处理会有所偏差
    可以看到在chrome 89的版本中,是按照微任务放置顺序来执行的;但在nodejs 10版本中,却是promise的优先级较高,在nodejs 11之后的版本就基本趋于一致了
  10. 这里我们且先按顺序的来说明,先输出async1 end
  11. 这时,再取下一次微任务promise2,输出promise2,这时产生一个promise3的微任务:
    宏任务:setTimeout
    微任务:1. promise3
  12. 微任务还是优先于宏任务,输出promise3
  13. 最后输出宏任务setTimeout

考查事件触发+异步结合


  
  

当手动点击按钮时,打印顺序是什么?----2143
当使用btn.click()时,打印顺序是一样的吗?----2413
两者为什么不一样?

当使用手动点击时,其实相当于每个监听事件,都是一个独立的函数作用域,相当于:

function A() {
  Promise.resolve().then(() => console.log('1'))
  console.log('2');
}
function B() {
  Promise.resolve().then(() => console.log('3'))
  console.log('4');
}
btn.addEventListener('click', A)
btn.addEventListener('click', B)

// 手动点击后,相当于组合调用
A()
B()

当执行A(),生成一个独立的执行上下文EC(A),整个A()中的执行过程,可以看成是一个事件循环tick,在这个事件循环中,先执行同步代码2,再执行微任务1;在下一个事件循环tickB()同理,所以结果是2143

当使用btn.click()时,相当于调用一个函数,这个函数会将所有事件监听的回调整合在同一个函数作用域内,也就是相当于:

function total() {
  // A
  Promise.resolve().then(() => console.log('1'))
  console.log('2');
  // B
  Promise.resolve().then(() => console.log('3'))
  console.log('4');
}

在这个函数作用域中,也是按顺序先执行同步代码2 => 4,然后再按顺序执行微任务1 => 3,所以结果为2413

你可能感兴趣的:(javascript的同步异步编程)