js事件队列

js事件队列

Posted by SkioFox on August 25, 2019

从promise、process.nextTick、setTimeout出发,谈谈Event Loop中的Job queue

  • Promise对象
      var promise1 = new Promise(function(resolve, reject) {
          setTimeout(function() {
              resolve('foo');
          }, 2000);
      });
    
      promise1.then(function(value) {
      console.log(value);
      // expected output: "foo"
      });
    
      console.log(promise1);
      // expected output: [object Promise]
    
      // 输出
    
      // [object Promise]
      // "foo"
    

    一个 Promise有以下几种状态:

      pending: 初始状态,既不是成功,也不是失败状态。
    
      fulfilled: 意味着操作成功完成。
    
      rejected: 意味着操作失败。
    

    avatar

  • Promise.all(iterable)

    iterable:一个可迭代对象,如 Array 或 String。

          var promise1 = Promise.resolve(3);
          var promise2 = 42;
          var promise3 = new Promise(function(resolve, reject) {
              setTimeout(resolve, 100, 'foo');
          });
          // Promise.all 等待所有都完成(或第一个失败)
          Promise.all([promise1, promise2, promise3]).then(function(values) {
              console.log(values);
          });
          // expected output: Array [3, 42, "foo"]
    
          // 如果参数中包含非 promise 值,这些值将被忽略,但仍然会被放在返回数组中(如果 promise 完成的话):
    
          // this will be counted as if the iterable passed is empty, so it gets fulfilled
          var p = Promise.all([1,2,3]);
          // this will be counted as if the iterable passed contains only the resolved promise with value "444", so it gets fulfilled
          var p2 = Promise.all([1,2,3, Promise.resolve(444)]);
          // this will be counted as if the iterable passed contains only the rejected promise with value "555", so it gets rejected
          var p3 = Promise.all([1,2,3, Promise.reject(555)]);
    
          // using setTimeout we can execute code after the stack is empty
          setTimeout(function(){
              console.log(p);
              console.log(p2);
              console.log(p3);
          });
    
          // logs
          // Promise { <state>: "fulfilled", <value>: Array[3] }
          // Promise { <state>: "fulfilled", <value>: Array[4] }
          // Promise { <state>: "rejected", <reason>: 555 }
    
  • 分析js中代码的执行顺序

    单线程:执行栈->栈尾->Event Loop

// 代码执行
    // 回调函数先加入任务队列,等到同步任务处理完(当前执行栈结束),才会从任务队列得到执行
    setTimeout(function(){console.log(1)},0);
    new Promise(function(resolve,reject){
         // 决定pending->fulfilled还是pending->rejected
        console.log(2);
        // 传参给成功的函数 
        // resolve(3); // 2,7,6,4,5,1
        // 加入延时 2,7,6,1,5,5
        // 原因:没有同步的resolve,因此promise.then在当前的执行队列中是不存在的,只有promise从pending转移到resolve,才会有then方法
        // setTimeout(()=>{
        //     resolve(4)
        // },0)
        // resolve(); // 2,7,6,NaN,5,1
        // reject("error") // 2,7,6,error,1
        // 没有上面resolve,reject的代码直接执行2,7,6,1,因为pending状态无法改变
    }).then(function(value){console.log(value+1) // resolve时执行
    }).then(function(){console.log(5)// resolve时执行
    }).catch(function(error){ // 捕获reject的错误
        console.log(error) 
    })
    // process.nextTick方法指定的回调函数,总是在当前”执行栈”的尾部触发
    process.nextTick(function(){console.log(6)});
    // 同步
    console.log(7);

    // 解析:setTimeout和Promise.then任务的执行优先级是如何定义的。

  • 在Job queue中的队列分为两种类型:macro-task和microTask。我们举例来看执行顺序的规定,我们设

macro-task队列包含任务: a1, a2 , a3

micro-task队列包含任务: b1, b2 , b3

执行顺序为,首先执行marco-task队列开头的任务,也就是 a1 任务,执行完毕后,在执行micro-task队列里的所有任务,也就是依次执行b1, b2 , b3,执行完后清空micro-task中的任务,接着执行marco-task中的第二个任务,依次循环。

了解完了macro-task和micro-task两种队列的执行顺序之后,我们接着来看,真实场景下这两种类型的队列里真正包含的任务(我们以node V8引擎为例),在node V8中,这两种类型的真实任务顺序如下所示:

macro-task队列真实包含任务(可以理解为宏观任务队列):

script(主程序代码),setTimeout, setInterval, setImmediate, I/O, UI rendering

micro-task队列真实包含任务(可以理解为微观任务队列):

process.nextTick, Promises, Object.observe, MutationObserver

由此我们得到的执行顺序应该为:

script(主程序代码)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering

在ES6中macro-task队列又称为ScriptJobs,而micro-task又称PromiseJobs

  • 实现一个简单的promise
    // 函数方式
    function myPromise(constructor){
        let self=this;
        self.status="pending" //定义状态改变前的初始状态
        self.value=undefined;//定义状态为resolved的时候的状态
        self.reason=undefined;//定义状态为rejected的时候的状态
        function resolve(value){
            //两个==="pending",保证了状态的改变是不可逆的
            if(self.status==="pending"){
                self.value=value;
                self.status="resolved";
            }
        }
        function reject(reason){
            //两个==="pending",保证了状态的改变是不可逆的
            if(self.status==="pending"){
                self.reason=reason;
                self.status="rejected";
            }
        }
        //捕获构造异常
        try{
            constructor(resolve,reject);
        }catch(e){
            reject(e);
        }
    }
    // 在myPromise的原型上定义链式调用的then方法
    myPromise.prototype.then=function(onFullfilled,onRejected){
        let self=this;
        switch(self.status){
            case "resolved":
                onFullfilled(self.value);
                break;
            case "rejected":
                onRejected(self.reason);
                break;
            default:       
        }
    }
    // 上述就是一个初始版本的myPromise,在myPromise里发生状态改变,然后在相应的then方法里面根据不同的状态可以执行不同的操作。

    var p=new myPromise(function(resolve,reject){resolve(1)});
    p.then(function(x){console.log(x)})
    //输出1
    
    // 但是这里myPromise无法处理异步的resolve.比如:
    var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});
    p.then(function(x){console.log(x)})
    //无输出
    // 类的方式
    class PromiseM {
        constructor (process) {
            this.status = 'pending'
            this.msg = ''
            process(this.resolve.bind(this), this.reject.bind(this))
            console.log(this)
            return this
        }
        resolve (val) {
            this.status = 'fulfilled'
            this.msg = val
        }
        reject (err) {
            this.status = 'rejected'
            this.msg = err
        }
        then (fufilled, reject) {
            if(this.status === 'fulfilled') {
                fufilled(this.msg)
            }
            if(this.status === 'rejected') {
                reject(this.msg)
            }
        }
    }

    var mm=new PromiseM(function(resolve,reject){
            resolve('123');
        });
        mm.then(function(success){
            console.log(success);
        },function(){
            console.log('fail!');
        });

进一步优化方案