几种常见异步方式

391 阅读7分钟

异步

并发:宏观概念,有两个任务 A、B,在一段时间内通过任务的切换,完成了两个任务

并行:微观概念,两核 CPU 同时完成那个两个任务

Generator

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

假如不传入参数,

var it = foo()
console.log(it.next()) // {value: 2 * (undefined + 1), done: false}
console.log(it.next()) // {value: undefined / 3, done: false}
console.log(it.next()) // {value: undefined + undefined + undefined, done: true}

传参数,next()传的参数将作为上一个 yield 的返回值

var it = foo(10)
console.log(it.next()) // {value: 11, done: false}
console.log(it.next(6)) // {value: 4, done: false}
console.log(it.next(4)) // {value: 26, done: true}

Promise

构造函数执行、then 函数执行的区别?

  • 构造函数是立即执行的
  • then 是异步的,调用 then 函数会返回一个新的 promise 对象,内部使用 return,会被包装成 resolve()

缺点:

  • 不能取消
  • 错误需要回调函数去捕捉?

async、await

    async function test() {
      return "test..."
    }
    async function test2() {
      await test().then(res => {
        console.log("res", res)
      })
      console.log("start")
    }
    test2()

普通函数前面加上 async

  • 执行这个函数就会返回一个 promise 对象。
  • 相当于将函数的返回值,包裹了一层 Promise.resolve(),跟 then 中处理返回值操作相当

缺点:没有依赖性的几个异步请求,还不如使用 Promise.all() ?

    let a = 0
    let b = async () => {
      a = a + await 10
      console.log('2', a) // -> '2' 10
    }
    b()
    a++
    console.log('1', a) // -> '1' 1

执行 b 函数,函数体有 await,await 会保留此时 a 的值 0,并将后面的表达式包裹一层 Promise.resolve()。执行函数体外的 a++,最后再执行 then 后面的异步

setTimeout、setInterval、requestAnimationFrame

前两个都不能保证在指定的延时后执行,比如在前边代码操作耗时较多时。可以计算当前时间,插值多少,动态设置延时时间,来达到按期执行的目的

另外 setInterval 有可能在耗时操作后,将多个回调函数同时执行,影响性能,所有尽量用 setTimeout 代替

requestAnimationFrame 自带函数节流,基本上 16.6ms,浏览器一帧执行一次,延时效果是精确的

手写 Promise

简版 Promise

实现目标:

    const PENDING = "pending"
    const RESOLVE = "resolve"
    const REJECT = "reject"

    function _Promise() {

    }
    new _Promise((resolve, reject) => {
      resolve("success")
    }).then(res => {
      console.log("res", res)
    })
    new _Promise((resolve, reject) => {
      setTimeout(() => {
        reject("fail")
      }, 100)
    }).then(res => {
      console.log("res", res)
    })
    function _Promise(fn) {
      const _this = this
      _this.state = PENDING
      // 返回结果
      _this.val = null
      _this.resolveCbs = []
      _this.rejectCbs = []

      // 只有当状态是pendding时,状态才能改变
      function resolve(val) {
        setTimeout(() => {
          if (_this.state === PENDING) {
            _this.state = RESOLVE
            _this.val = val
            _this.resolveCbs.forEach(cb => cb(_this.val));
          }
        }, 0)
      }

      function reject(val) {
        setTimeout(() => {
          if (_this.state === PENDING) {
            _this.state = REJECT
            _this.val = val
            _this.rejectCbs.forEach(cb => cb(_this.val));
          }
        }, 0)
      }
      // 执行传进来的函数,并传入resolve、reject
      try {
        fn(resolve, reject)
      } catch(err) {
        reject(err)
      }
    }
    // 如果传入的函数是延时的,那么当前state是pedding,将两个回调都push进去
    // 立即执行的函数,立即修改state,并执行回调
    _Promise.prototype.then = function (resolveCb, rejectCd) {
      const _this = this
      resolveCb = typeof resolveCb === "function" ? resolveCb : val => val
      rejectCd = typeof rejectCd === "function" ? rejectCd : val => {
        throw new Error(val)
      }
      if (_this.state === PENDING) {
        this.resolveCbs.push(resolveCb)
        this.rejectCbs.push(rejectCd)
      }
      if (_this.state === RESOLVE) {
        resolveCb(_this.val)
      }
      if (_this.state === REJECT) {
        rejectCd(_this.val)
      }
    }

Promise/A+ ?

后边这一坨没看太懂....

_Promise.prototype.then = function (resolveCb, rejectCd) {
      const _this = this
      resolveCb = typeof resolveCb === "function" ? resolveCb : val => val
      rejectCd = typeof rejectCd === "function" ? rejectCd : val => {
        throw new Error(val)
      }
      if (_this.state === PENDING) {
        let promise2
        return promise2 = new _Promise((resolve, reject) => {
          this.resolveCbs.push(() => {
            try {
              const x = resolveCb(_this.val)
              resolutionProcedure(promise2, x, resolve, reject)
            } catch (err) {
              reject(err)
            }
          })
          this.rejectCbs.push(() => {
            try {
              const x = rejectCd(_this.val)
              resolutionProcedure(promise2, x, resolve, reject)
            } catch (err) {
              reject(err)
            }
          })
        })
      }
      if (_this.state === RESOLVE) {
        let promise2
        return promise2 = new _Promise((resolve, reject) => {
          setTimeout(() => {
            try {
              const x = resolveCb(_this.val)
              resolutionProcedure(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
      }
      if (_this.state === REJECT) {
        let promise2
        return promise2 = new _Promise((resolve, reject) => {
          setTimeout(() => {
            try {
              const x = rejectCd(_this.val)
              resolutionProcedure(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
      }
    }

    function resolutionProcedure(promise2, x, resolve, reject) {
      // 返回值不能与结果相等,避免循环依赖
      if (promise2 === x) {
        return reject(new Error("err"))
      }
      if (x instanceof _Promise) {
        x.then(function (res) {
          resolutionProcedure(promise2, res, resolve, reject)
        }, reject)
      }
      let called = false
      if (x !== null && (typeof x === "object" || typeof x === "function")) {
        try {
          let then = x.then
          if (typeof then === "function") {
            then.call(x, y => {
              if (called) {
                return
              }
              called = true
              resolutionProcedure(promise2, y, resolve, reject)
            }, e => {
              if (called) {
                return
              }
              called = true
              reject(e)
            })
          } else {
            reject(x)
          }
        } catch (e) {
          if (called) {
            return
          }
          called = true
          reject(e)
        }
      } else {
        reject(x)
      }
    }

Event Loop

线程、进程

线程是进程的最小单元。比如浏览器打开一个 tab,就是开启了一个进程,这个进程又包含了 js 线程、http 请求线程、渲染线程等。当发起一个请求时,就创建了一个线程,请求结束,线程销毁。

浏览器每打开一个 tab 就是开启了一个进程,然而浏览器内核是多线程的,包括 js 引擎线程、DUI 渲染线程、定时器线程、事件触发线程、http 异步线程等。

执行栈

当执行一个函数的时候,js 会生成与这个函数相对应的 context 执行上下文,这个执行环境包含了函数内作用域、父的作用域、this、变量等。当很多函数被依次调用时,由于 js 是单线程,一次只能执行一个函数,所以就把这些函数排队放到栈中,也叫执行栈。每当要执行某个函数,就将函数入栈,执行完,出栈。

单线程、非阻塞

  1. 浏览器有多个线程,js 是单线程的,同时间只能做一件事。
  2. js 又是非阻塞的,当 js 执行到一段异步任务时,就是通过其他线程来完成的,比如网络请求、定时器,是分别交给定时器线程、http 请求线程来做。处理完成后,达到了触发条件,再将其回调添加到任务队列中,等待 js 主线程空置,再添加到执行栈执行。
  3. js 引擎线程和渲染线程是互斥的,js 运行的时候可能会阻断渲染线程。以防止 js 线程运行的时候,渲染线程还在工作。

宏任务、微任务

宏任务:script、定时器、netWork 微任务: Promise、process.nextTick(node.js)、MutationObserver(vue2.$nextTick实现)、Object.observe(已废弃)

事件循环

  1. 执行 js 代码,就是往执行栈放入函数,一段 script 脚本,就是宏任务的开始。
  2. 当遇到异步代码,先将这个异步代码 pending 挂起,比如一个网络请求,是在异步请求线程执行完,将回调放到任务队列中(宏任务队列、微任务队列)
  3. 当执行栈清空,会依次清空当前微任务队列。假如微任务中又产生了新的微任务,又会通过 Web apis,将其回调添加到微任务队列。直到清空微任务队列。
  4. 清空完微任务 就将在需要的时候假如队列中。当执行栈空了以后,就从任务队列中取出要执行的代码,放入执行栈执行。

先分析个简单的:

setTimeout(_ => console.log(4))

new Promise(resolve => {
  resolve()
  console.log(1)
}).then(_ => {
  console.log(3)
})

console.log(2)
  • 代码都是在 script 标签里的,那么整个代码块就作为第一个宏任务,开始宏任务
  • setTimeout 入栈,是定时器,出栈。webapi 辅助线程等到延时 0 秒后,将其回调放到宏任务队列的末尾
  • new Promise 入栈,执行代码块。webapi 辅助线程将 resolve 回调放到微任务队列的末尾,输出1, 2
  • 下次宏任务开始前,检查微任务队列,微任务队首出队,输出3。清空微任务,渲染 ui
  • 执行下次宏任务队列,宏任务队首出队,输出4
console.log('script start')
async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end')
}
async1()
setTimeout(function () {
  console.log('setTimeout')
}, 0)
new Promise(resolve => {
    console.log('Promise')
    resolve()
  })
  .then(function () {
    console.log('promise1')
  })
  .then(function () {
    console.log('promise2')
  })
console.log('script end')
  • 开始宏任务,输出script start
  • 后执行 async1(), await 相当于把后面的执行结果包了一层 Promise.resolve,函数式立即执行的,输出async2 end。后边的放到微任务队列末尾
  • setTimeout 的回调,0 秒后放到宏任务队列末尾
  • new Promise 立即执行,输出Promise。同样将 then 的回调放到微任务末尾。
  • 输出script end
  • 本轮宏任务结束之前,先清空微任务队列,async1 函数的回调出队,输出async1 end
  • promise1 回调出队,输出promise1,将 promise2 回调假如到微任务末尾。
  • 再次清空微任务队列,输出promise2。GUI 渲染,本轮宏任务结束
  • 执行下一步宏任务 setTimeout 回调出队,输出setTimeout
script start, async2 end, Promise, script end, async1 end, promise1, promise2, setTimeout

node Event Loop