浅析promise跟事件循环(event loop)

537 阅读5分钟

事件循环

js是一个单线程语言,意味着所有的任务都只能顺序执行,如果所有的任务都是同步进行的,那么如果进行一个请求,就必须要等待请求结果返回,才能去做其他的事情,这如果是一个很慢的请求,那么对于交互而言,无疑是灾难性的,所以异步任务尤为重要,注册一个回调事件,当异步任务完成之后,再去执行回调函数。

宏任务 微任务

举个例子,大爷去银行排队办理业务,那么一个大爷就是一个宏任务,大爷办的一个业务就是一个微任务,一个大爷可能会办好几个业务

哪些是宏任务

  • IO
  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame
  • UI rendering

哪些是微任务

  • Promise.then
  • async await
  • process.nextTick
  • MutationObserver

事件循环的进程模型

  • 选择当前要执行的任务队列,选择任务队列中最先进入的任务,如果任务队列为空即null,则执行跳转到微任务(MicroTask)的执行步骤。
  • 将事件循环中的任务设置为已选择任务。
  • 执行任务。
  • 将事件循环中当前运行任务设置为null。
  • 将已经运行完成的任务从任务队列中删除。
  • microtasks步骤:进入microtask检查点。
  • 更新界面渲染。
  • 返回第一步。

实现一个事件的发布订阅

    class eventEmit {
        constructor(){
            this.events = {}
        }
        
        on(event, fn) {
            if(!this.events[event]){
                this.events[event] = [fn];
            } else {
                this.events[event].push(fn)
            }
        }
        
        off(event, fn) {
            if(!this.events[event] || !this.events[event][fn]) {
                throw new Error('无此事件');
                return;
            }
            
            this.events[event].filter(item => item !== fn)
        }
        
        once(event, fn) {
            fn.once = true;
            this.on(event, fn);
        }
        
        emit(event, data) {
            this.events[event].forEach(cb => {
                cb(data);
                if(cb.once){
                    this.off(event, cb);
                }
            })
        }
    }

Promise

promise是es6提出的概念,本质是一个状态机,有三种状态,分别是pedding、fullfilled、rejected,状态的改变是单向的,只能从pedding变成其他两种状态,内置then方法,then方法返回一个promise,所以then方法可以被链式调用。

实现一个Promise

  function myPromise(exector) {
      let self = this;
      self.status = "pedding";
      self.data = undefined;
      self.onResolvedCallback = [];
      self.onRejectCallback = [];

      function resolve(value) {
        if (self.status === "pedding") {
          self.status = "fullfilled";
          self.data = value;
          for (let i = 0; i < self.onResolvedCallback.length; i++) {
            self.onResolvedCallback[i](value);
          }
        }
      }

      function reject(reason) {
        if (self.status === "pedding") {
          self.status = "rejected";
          self.data = reason;
          for (let i = 0; i < self.onRejectCallback.length; i++) {
            self.onRejectCallback[i](reason);
          }
        }
      }
      try {
        exector(resolve, reject);
      } catch (e) {
        reject(e);
      }
}

实现then方法 考虑链式调用

  myPromise.prototype.then = (onResolved, onRejected) => {
      let self = this;
      let promise2;
      onResolved = typeof onResolved === "function" ? onResolved : () => {};
      onRejected = typeof onRejected === "function" ? onRejected : () => {};
      if (self.status === "fullfilled") {
        return (promise2 = new myPromise(function (resolve, reject) {
          try {
            let x = onResolved(self.data);
            if (x instanceof myPromise) {
              x.then(resolve, reject);
            }
            resolve(x);
          } catch (e) {
            reject(e);
          }
        }));
      }

      if (self.status === "rejected") {
        return (promise2 = new myPromise(function (resolve, reject) {
          try {
            let x = onRejected(self.data);
            if (x instanceof myPromise) {
              x.then(resolve, reject);
            }
          } catch (e) {
            reject(e);
          }
        }));
      }

      if (self.status === "pedding") {
        return (promise2 = new myPromise(function (resolve, reject) {
          self.onResolvedCallback.push(function () {
            try {
              let x = onResolved(self.data);
              if (x instanceof Promise) {
                x.then(resolve, reject);
              }
            } catch (e) {
              reject(e);
            }
          });
        }));
      }
    };

实现一个promise.all

    function promiseAll(promises) {
        if(!Array.isArray(promises)) {
            throw new Error('传参错误')
        }
        let n = 0;
        let result = [];
        promises.forEach((item, index) => {
            Promise.resolve().then(res => {
                 result[index] = res;
                 n++;
                 if(n === promises.length){
                     Promise.resolve(result)
                 }
            }).catch(e => {
                Promise.reject(e);
            })
        })
    }

实现一个promise.race

    function promiseRace(promises){
        return new Promise(resolve, reject) {
            for(let i in promises) {
                Promise.resolve(i).then(res => {
                    resolve(res);
                }, e => reject(e))
            }
        }
    }

async await的产生背景

因为promise虽然解决了回调地狱的问题,但是写法来看,还是存在的链式调用不雅观的问题,特别是多个then链式调用的时候, 所以await是用来利用同步的方式去调用异步的代码。

  request(5).then(res1 => {
      console.log(res1) // 1秒后 输出 10
      request(res1).then(res2 => {
    console.log(res2) // 2秒后 输出 20
  })
})

async function fn () {
  const res1 = await request(5)
  const res2 = await request(res1)
  console.log(res2) // 2秒后输出 20
}
fn()

async的要点

  • await只能在含有async的函数里使用
  • async函数返回一个Promise,是否有值,取决于是否return
  • await最好是接一个promise,虽然用其他的也可以达到排队的效果
  • async/await的作用是用同步的方式去执行异步的代码
  • async函数其实是语法糖,本质是generator函数

generator函数

    function* gen() {
      yield 1
      yield 2
      yield 3
    }
    const g = gen()
    console.log(g.next()) // { value: 1, done: false }
    console.log(g.next()) // { value: 2, done: false }
    console.log(g.next()) // { value: 3, done: false }
    console.log(g.next()) // { value: undefined, done: true }

generator函数跟普通函数相比,就是在函数名前面多一个*,只有在generator函数内,才能使用yield,yield相当于是暂停器,可以使用next方法去继续执行,next方法会返回上一步yield的返回值跟是否执行完成的状态标志done。

yield接函数

    function fn(num) {
      console.log(num)
      return num
    }
    function* gen() {
      yield fn(1)
      yield fn(2)
      return 3
    }
    const g = gen()
    console.log(g.next()) 
    // 1
    // { value: 1, done: false }
    console.log(g.next())
    // 2
    //  { value: 2, done: false }
    console.log(g.next()) 
    // { value: 3, done: true }

yield接Promise

    function fn(num) {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(num)
        }, 1000)
      })
    }
    function* gen() {
      yield fn(1)
      yield fn(2)
      return 3
    }
    const g = gen()
    console.log(g.next()) // { value: Promise { <pending> }, done: false }
    console.log(g.next()) // { value: Promise { <pending> }, done: false }
    console.log(g.next()) // { value: 3, done: true }

如果我们想要promise的结果,那么应该怎么处理呢?

    const g = gen()
    const next1 = g.next()
    next1.value.then(res1 => {
      console.log(next1) // 1秒后输出 { value: Promise { 1 }, done: false }
      console.log(res1) // 1秒后输出 1

      const next2 = g.next()
      next2.value.then(res2 => {
        console.log(next2) // 2秒后输出 { value: Promise { 2 }, done: false }
        console.log(res2) // 2秒后输出 2
        console.log(g.next()) // 2秒后输出 { value: 3, done: true }
      })
    })

如果想要传递参数

    function fn(nums) {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(nums * 2)
        }, 1000)
      })
    }
    function* gen() {
      const num1 = yield fn(1)
      const num2 = yield fn(num1)
      const num3 = yield fn(num2)
      return num3
    }
    const g = gen()
    const next1 = g.next()
    next1.value.then(res1 => {
      console.log(next1) // 1秒后同时输出 { value: Promise { 2 }, done: false }
      console.log(res1) // 1秒后同时输出 2

      const next2 = g.next(res1) // 传入上次的res1
      next2.value.then(res2 => {
        console.log(next2) // 2秒后同时输出 { value: Promise { 4 }, done: false }
        console.log(res2) // 2秒后同时输出 4

        const next3 = g.next(res2) // 传入上次的res2
        next3.value.then(res3 => {
          console.log(next3) // 3秒后同时输出 { value: Promise { 8 }, done: false }
          console.log(res3) // 3秒后同时输出 8

           // 传入上次的res3
          console.log(g.next(res3)) // 3秒后同时输出 { value: 8, done: true }
        })
      })
    })

实现一个async/await

    function generatorToAsync(generatorFn) {
      return function() {
        const gen = generatorFn.apply(this, arguments) // gen有可能传参

        // 返回一个Promise
        return new Promise((resolve, reject) => {

          function go(key, arg) {
            let res
            try {
              res = gen[key](arg) // 这里有可能会执行返回reject状态的Promise
            } catch (error) {
              return reject(error) // 报错的话会走catch,直接reject
            }

            // 解构获得value和done
            const { value, done } = res
            if (done) {
              // 如果done为true,说明走完了,进行resolve(value)
              return resolve(value)
            } else {
              // 如果done为false,说明没走完,还得继续走

              // value有可能是:常量,Promise,Promise有可能是成功或者失败
              return Promise.resolve(value).then(val => go('next', val), err => go('throw', err))
            }
          }

          go("next") // 第一次执行
        })
      }
    }

    function* gen() {
        const num1 = yield fn(1)
        const num2 = yield fn(num1)
        const num3 = yield fn(num2)
        return num3
    }
    const asyncFn = generatorToAsync(gen)

    asyncFn().then(res => console.log(res))