Skip to content
On this page

异步

  • 同步

    • 指在 主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务
    • 也就是调用一旦开始,必须这个调用 返回结果(划重点——)才能继续往后执行。程序的执行顺序和任务排列顺序是一致的。
  • 异步

    • 异步任务是指不进入主线程,而进入 任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
    • 程序的执行顺序和任务的排列顺序是不一致的。
    • 用的setTimeout和setInterval函数,Ajax都是异步操作
  • 实现异步的方法

    • 回调函数(Callback)、事件监听、发布订阅、Promise/A+、生成器Generators/ yield、async/await

Promise

  • ES6中的Promise 是异步编程的一种方案。从语法上讲,Promise 是一个对象,它可以获取异步操作的消息。
  • Promise对象, 可以将异步操作以同步的流程表达出来。使用 Promise 主要有以下好处:
    • 可以很好地解决回调地狱的问题
    • 语法非常简洁。Promise 对象提供了简洁的API,使得控制异步操作更加容易。
  • 状态的表现
    • pending状态,不会触发then和catch
    • resolved状态,会触发后续的then(onResloved)回调函数
    • rejected状态,会触发then(null, onRejected),没有则触发后续的catch回调函数
  • then和catch改变状态
    • then正常返回resolved,里面报错则返回rejected
    • catch正常也返回resolved,里面报错则返回rejected
    • catch是一个语法糖,相当于调用Prmise.prototype.then(null, onRejected)
  • Promise.all(iterable)
    • 语法:var p = Promise.all([p1, p2, p3]);
    • Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise实例,如果不是,就会先调用Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)
    • (1) 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
    • (2) 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
  • Promise.race(iterable)
    • 语法:var p = Promise.race([p1, p2, p3]);
    • Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。
    • Promise.race方法的参数与Promise.all方法一样,如果不是Promise实例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。

defer和async

  • 区别主要在于一个执行时间,defer会在文档解析完之后执行,并且多个defer会按照顺序执行,而async则是在js加载好之后就会执行,并且多个async,哪个加载好就执行哪个

生成器Generators/ yield

  • Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 最大的特点就是可以控制函数的执行。

  • 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

  • Generator 函数除了状态机,还是一个遍历器对象生成函数

  • 可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果

  • yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

  • js
    function *foo(x) {
      let y = 2 * (yield (x + 1))
      let z = yield (y / 3)
      return (x + y + z)
    }
    let it = foo(5)
    console.log(it.next())   // => {value: 6, done: false}
    console.log(it.next(12)) // => {value: 8, done: false}
    console.log(it.next(13)) // => {value: 42, done: true}
    
    • 首先 Generator 函数调用和普通函数不同,它会返回一个迭代器
    • 当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6
    • 当执行第二次 next 时,传入的参数12就会被当作上一个yield表达式的返回值,如果你不传参,yield 永远返回 undefined。此时 let y = 2 * 12,所以第二个 yield 等于 2 * 12 / 3 = 8
    • 当执行第三次 next 时,传入的参数13就会被当作上一个yield表达式的返回值,所以 z = 13, x = 5, y = 24,相加等于 42

event-loop(事件循环/轮询)

  • js是单线程运行的,异步(setTimeout, ajax)要基于回调实现,event loop就是**异步回调的实现原理,**DOM事件也使用回调,基于event loop

  • js的执行

    • 从前到后一行一行执行
    • 如果某一行报错,则停止下面代码的执行
    • 先执行同步,再执行异步
  • event loop过程1

    • 同步代码,一行一行放到call stack执行
    • 遇到异步,先记录下,等待时机(定时、网络请求等),时机到了,就移动到callback quene
  • event loop过程2

    • 如果call stack为空(即同步代码执行完)event loop开始工作
    • 轮询查找callback quene,如有任务则移动到call stack中执行
    • 然后反复轮 询查找

async/await

  • async/await 语法上,以同步的方式写的代码能够异步执行,是消灭异步回调的终极武器,但和Promise并不排斥,两者相辅相成

  • async/await和Promise的关系

    • 执行async函数,返回的是Prmoise对象
    • await相当于Promise的then
    • try...catch可捕获异常,代替了Prmoise的catch
  • 执行顺序

    • js
      async function async1(){
        console.log('async1 start');// 2
        await async2()  //undefined
        // await 后面的内容都可以看作callback里的内容,即异步
        console.log('async1 center'); // 5
        await async3()
        console.log('async1 end'); // 7
      }
      
      async function async2(){
        console.log('async2');// 3
      }
      async function async3(){
        console.log('async3'); // 6
      }
      
      console.log('script start');// 1
      async1()
      console.log('script end');// 4
      

for ... of

  • for ... in(以及forEach for) 是常规的同步遍历

  • for ... of 常用于异步的遍历

    • js
      function muti(num) {
        return new Promise((reslove, reject) => {
          setTimeout(() => {
            reslove(num * num);
          }, 1000);
        });
      }
      const nums = [1, 2, 3]
      
      // 一秒后同时打印1 4 9
      nums.forEach(async (item) => {
        const res = await muti(item)
        console.log(res);
      });
      
      // 每次打印间隔一秒
      !(async function(){
        for(let i of nums){
          const res = await muti(i)
          console.log(res);
        }
      })()
      

微任务/宏任务

  • 宏任务:setTimeout, setInterval, Ajax, DOM事件

  • 微任务:Promise async/await

  • 微任务执行的时机比宏任务早

  • event loop和DOM渲染

    1. 每次call stack清空(每次轮询结束),同步代码执行完毕
    2. 执行当前的微任务,从micro task queue取出
    3. 尝试DOM的渲染
    4. 然后再去触发下一次event loop
  • 微任务/宏任务的区别

    • 宏任务:DOM渲染后触发,如seTimeout,是由浏览器规定的

    • 微任务:DOM渲染前触发,如Promise,是由ES6语法规定的

    • js
      const box = document.getElementById('box')
      const p = document.createElement('p')
      p.innerText = 'this is p'
      box.appendChild(p)
      
      // 微任务:DOM渲染前触发
      Promise.resolve().then(()=>{
        console.log(p.innerText);
        alert('Promise')  //此时DOM未渲染
      })
      
      // 宏任务:DOM渲染后触发
      setTimeout(()=>{
        console.log(p.innerText);
        alert('setTimeout') //此时DOM渲染完成
      })
      
  • 从event loop解释,为何微任务执行更早

    • 当执行微任务时,会等待时机,时机到后放入微任务队列
    • 而event loop会在call stack清空后先将所有的微任务一个一个取出来执行
    • 再尝试DOM的渲染,最后取出callback quene中的任务执行