zoukankan      html  css  js  c++  java
  • JS的运行机制 Event Loop

    1.JS是单线程

    JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

    所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征。

    为了利用多核CPU的计算能力,HTML5提出Web Worker标准,
    允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

    它的作用就是给JS创造多线程运行环境,允许主线程创建worker线程,分配任务给后者,主线程运行的同时worker线程也在运行,相互不干扰,在worker线程运行结束后把结果返回给主线程。这样做的好处是主线程可以把计算密集型或高延迟的任务交给worker线程执行,这样主线程就会变得轻松,不会被阻塞或拖慢。++这并不意味着JS语言本身支持了多线程能力,而是浏览器作为宿主环境提供了JS一个多线程运行的环境。++
    不过因为worker一旦新建,就会一直运行,不会被主线程的活动打断,这样有利于随时响应主线程的通性,但是也会造成资源的浪费,所以不应过度使用,用完注意关闭。

    for (var i = 0; i < 5; i++) {
      console.log(i);
    }
    
    答案
     
    0
    1
    2
    3
    4
      

    for (var i = 0; i < 5; i++) {
      setTimeout(function() {
        console.log(i);
      }, 1000 * i);
    }
    
    答案
     
    5个5
      

    怎样输出0到4 间隔1秒

    答案
     
    for (var i = 0; i < 5; i++) {
      (function(i) {
        setTimeout(function() {
          console.log(i);
        }, i * 1000);
      })(i);
    }
      

    删掉i

    for (var i = 0; i < 5; i++) {
      (function() {
        setTimeout(function() {
          console.log(i);
        }, i * 1000);
      })(i);
    }
    
    答案
     
    5个5
      

    for (var i = 0; i < 5; i++) {
      setTimeout((function(i) {
        console.log(i);
      })(i), i * 1000);
    }
    
    答案 立即输出0到4
     
    for (var i = 0; i <5; i++) {
      var temp = (function(i) {
        console.log(i);
      })(i);
      setTimeout(temp; i*1000)
    }
    for (var i = 0; i <5; i++) {
      (function(i) {
        console.log(i);
      })(i)
    }
    for (var i = 0; i <5; i++) {
      console.log(i);
    }
      

    setTimeout(function() {
      console.log(1)
    }, 0);
    new Promise(function executor(resolve) {
      console.log(2);
      for( var i=0 ; i<10000 ; i++ ) {
        i == 9999 && resolve();
      }
      console.log(3);
    }).then(function() {
      console.log(4);
    });
    console.log(5);
    
    答案
     
    2 3 5 4 1
      

    • JS 分为同步任务和异步任务
    • 同步任务都在JS引擎线程上执行,形成一个执行栈
    • 事件触发线程管理一个任务队列,异步任务触发条件达成,将回调事件放到任务队列中
    • 执行栈中所有同步任务执行完毕,此时JS引擎线程空闲,系统会读取任务队列,将可运行的异步任务回调事件添加到执行栈中,开始执行
      image
      image

    宏任务 macrotask

    每次执行栈执行的代码是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行), 每一个宏任务会从头到尾执行完毕,不会执行其他。

    JS引擎线程和GUI渲染线程是互斥的关系,浏览器为了能够使宏任务和DOM任务有序的进行,会在一个宏任务执行结果后,在下一个宏任务执行前,GUI渲染线程开始工作,对页面进行渲染。

    宏任务-->渲染-->宏任务-->渲染
    宏任务主要有:script(整体代码)、setTimeout、setInterval、I/O、UI 交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)

    document.body.style = 'background:black';
    document.body.style = 'background:red';
    document.body.style = 'background:blue';
    document.body.style = 'background:grey';
    
    答案
     
    页面背景会在瞬间变成灰色,以上代码属于同一次宏任务,
    所以全部执行完才触发页面渲染,渲染时GUI线程会将所有UI改动优化合并,
    所以视觉效果上,只会看到页面变成灰色。
      

    document.body.style = 'background:blue';
    setTimeout(function(){
        document.body.style = 'background:black'
    },0)
    
    答案
     
    页面先显示成蓝色背景,然后瞬间变成了黑色背景,
    这是因为以上代码属于两次宏任务,
    第一次宏任务执行的代码是将背景变成蓝色,然后触发渲染,
    将页面变成蓝色,再触发第二次宏任务将背景变成黑色。
      

    微任务microtask

    宏任务结束后,会执行渲染,然后执行下一个宏任务, 微任务是当前宏任务执行后立即执行的任务。

    也就是说,当宏任务执行完,会在渲染前,将执行期间所产生的所有微任务都执行完。

    微任务主要有:
    requestAnimationFrame:浏览器环境 ????
    MutationObserver:浏览器环境
    Promise.prototype.then
    Promise.prototype.catch
    Promise.prototype.finally
    process.nextTick:Node环境
    queueMicrotask

    document.body.style = 'background:blue'
    console.log(1);
    Promise.resolve().then(()=>{
        console.log(2);
        document.body.style = 'background:black'
    });
    console.log(3);
    
    答案
     
    控制台输出 1 3 2 , 是因为 promise 对象的 then 方法的回调函数是异步执行,所以 2 最后输出
    页面的背景色直接变成黑色,没有经过蓝色的阶段,是因为,我们在宏任务中将背景设置为蓝色,但在进行渲染前执行了微任务,
    在微任务中将背景变成了黑色,然后才执行的渲染
      

    image

    • 首先,整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为同步任务、异步任务两部分
    • 同步任务会直接进入主线程依次执行
    • 异步任务会再分为宏任务和微任务
    • 宏任务进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中
    • 微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中
    • 当主线程内的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务

    上述过程会不断重复,这就是Event Loop,比较完整的事件循环

    function test() {
      console.log(1)
      setTimeout(function () {
        console.log(2)
      }, 1000)
    }
    
    test();
    
    setTimeout(function () {
      console.log(3)
    })
    
    new Promise(function (resolve) {
      console.log(4)
      setTimeout(function () {
        console.log(5)
      }, 100)
      resolve()
    }).then(function () {
      setTimeout(function () {
        console.log(6)
      }, 0)
      console.log(7)
    })
    
    console.log(8)
    
    答案
     
    1,4,8,7,3,6,5,2
      

    async/await

    async/await本质上还是基于Promise的一些封装,而Promise是属于微任务的一种

    所以在使用await关键字与Promise.then效果类似

    setTimeout(() => console.log(4))
    
    async function test() {
      console.log(1)
      await Promise.resolve()
      console.log(3)
    }
    
    test()
    
    console.log(2)
    
    
    答案
     
    1 2 3 4
    await 以前的代码,相当于与 new Promise 的同步代码,
    await 以后的代码相当于 Promise.then的异步
      

    requestAnimationFrame是不是微任务?

    requestAnimationFrame简称rAF,
    用来做动画效果,因为其回调函数执行频率与浏览器屏幕刷新频率保持一致,也就是通常说的它能实现60FPS的效果。在rAF被大范围应用前,我们经常使用setTimeout来处理动画。但是setTimeout是宏任务,在主线程繁忙时,不一定能及时地被调度,从而出现卡顿现象。

    function recursionRaf() {
    	requestAnimationFrame(() => {
            console.log('raf回调')
            recursionRaf()
        })
    }
    recursionRaf();
    

    在无限递归的情况下,rAF回调正常执行,浏览器也可正常交互,没有出现阻塞的现象。

    function recursionMicrotask() {
    	Promise.resolve().then(() => {
    		recursionMicrotask()
    	})
    }
    recursionMicrotask();
    

    页面会卡死 Microtask占着Main Thread不释放,浏览器渲染都没办法进行了

    rAF的任务级别是很高的,拥有单独的队列维护。在浏览器1帧的周期内,rAF与Javascript执行,浏览器重绘是同一个Level的。


    宏任务是否一定伴随着渲染?

    setTimeout(() => {
      document.body.style.background = "red"
      setTimeout(() => {
        document.body.style.background = "blue"
      })
    })
    
    答案 结果不可控,如果用setTimeout做动画,很容易掉帧
    requestAnimationFrame(() => {
        document.body.style.background = "red"
        requestAnimationFrame(() => {
          document.body.style.background = "blue"
        })
      })
    
    答案 rAF在浏览器决定渲染之前给你最后一个机会去改变 DOM 属性,然后很快在接下来的绘制中帮你呈现出来,所以这是做流畅动画的最佳选择。
    setTimeout(() => {
      console.log("sto")
      requestAnimationFrame(() => console.log("rAF"))
    })
    setTimeout(() => {
      console.log("sto")
      requestAnimationFrame(() => console.log("rAF"))
    })
    
    queueMicrotask(() => console.log("mic"))
    queueMicrotask(() => console.log("mic"))
    
    

    常规理解,宏任务之间理应穿插渲染,但浏览器会合并这两个定时器

    事件循环不一定每轮都伴随着重渲染,但是如果有微任务,一定会伴随着微任务执行。


    至此已大致理解了宏任务 微任务 及他们的执行时机
    加深

    Task和Microtask队列

    HTML标准
    Essentially, task sources are used within standards to separate logically-different types of tasks, which a user agent might wish to distinguish between. Task queues *are used by user agents to coalesce task sources within a given event loop。

    Task根据task source的不同,安排了独立的队列。比如Dom事件属于Task,但是Dom事件有很多种类型,为了方便user agent细分Task并精细化地安排各种不同类型Task的处理优先级,甚至做一些优化工作,必须有一个task source来区分。同理,Microtask也有自己的microtask task source。

    javascript是事件驱动的,在每一轮Event Loop中,会取出第一个runnable的Task(第一个可执行的Task,并不一定是顺序上的第一个Task)进入Main Thread执行,然后再检查Microtask队列并执行队列中所有Microtask。

    什么时候进队列

    异步任务有注册,进队列,回调被执行这三个关键行为。注册很好理解,代表这个任务被创建了;回调被执行则代表着这个任务已经被主线程捞起并执行了。但是,在进队列这一行为上,宏任务和微任务的表现是不一样的。

    宏任务进队列

    对于Task而言,任务注册时就会进入队列,只是任务的状态还不是runnable,不具备被Event Loop捞起的条件。

    document.body.addEventListener('click', function(e) {
        console.log('被点击了', e)
    })
    

    当addEventListener这行代码被执行时,任务就注册了,代表有一个用户点击事件相关的Task进入任务队列。这个宏任务什么时候才变成runnable呢?是用户点击发生并且信号传递到浏览器Render Process的Main Thread后,此时宏任务变成runnable状态,才可以被Event Loop捞起,进入Main Thread执行。

    宏任务:注册(马上进队列)(是否是runnable 未知) => 被触发 => 执行

    微任务进队列

    执行完一个Task后,如果Microtask队列不为空,会把Microtask队列中所有的Microtask都取出来执行 所以 Microtask在变为runnable状态时才进入Microtask队列。

    var promise1 = new Promise((resolve, reject) => {
        resolve(1);
    })
    promise1.then(res => {
        console.log('promise1微任务被执行了')
    })
    
    

    then 时被注册 resolve后 也就是Promise状态发生转移时 Promise状态由pending转移为fulfilled 时 变成runnable 然后进入队列

    var promise1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
        }, 0);
    });
    promise1.then(res => {
        console.log('promise1微任务被执行了');
    });
    
    

    Promise微任务的注册和进队列并不在同一次Event Loop
    两者中间隔了至少一次Event Loop。

    Promise.resolve().then(() => {
        console.log(0);
        return Promise.resolve(4);
    }).then((res) => {
        console.log(res)
    })
    
    Promise.resolve().then(() => {
        console.log(1);
    }).then(() => {
        console.log(2);
    }).then(() => {
        console.log(3);
    }).then(() => {
        console.log(5);
    }).then(() =>{
        console.log(6);
    })
    
    
    答案
     
    0 1 2 3 4 5 6
      

    深入了解promise

    根据promise/A+规范 实现一个promise

    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

    所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

    Promise对象有以下两个特点。

    许下承诺(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

    订阅承诺(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

    链式传播
    有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

    Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

    1.Promise 基本结构

    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('FULFILLED')
      }, 1000)
    })
    
    //构造函数Promise必须接受一个函数作为参数,称该函数为handle,handle又包含resolve和reject两个参数,它们是两个函数.
    

    定义一个判断一个变量是否为函数的方法

    // 判断变量否为function
    const isFunction = variable => typeof variable === 'function'
    

    定义一个名为 MyPromise 的 Class,它接受一个函数 handle 作为参数

    class MyPromise {
      constructor (handle) {
        if (!isFunction(handle)) {
          throw new Error('MyPromise must accept a function as a parameter')
        }
      }
    }
    
    1. Promise 状态和值

    Promise 对象存在以下三种状态:

    • Pending(进行中)

    • Fulfilled(已成功)

    • Rejected(已失败)

    //状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected ,且状态改变之后不会在发生变化,会一直保持这个状态。
    

    Promise的值是指状态改变时传递给回调函数的值

    handle函数包含 resolve 和 reject 两个参数,它们是两个函数,可以用于改变 Promise 的状态和传入 Promise 的值

    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('FULFILLED')
      }, 1000)
    })
    

    这里 resolve 传入的 "FULFILLED" 就是 Promise 的值

    resolve 和 reject

    • resolve : 将Promise对象的状态从 Pending(进行中) 变为 Fulfilled(已成功)
    • reject : 将Promise对象的状态从 Pending(进行中) 变为 Rejected(已失败)
    • resolve 和 reject 都可以传入任意类型的值作为实参,表示 Promise 对象成功(Fulfilled)和失败(Rejected)的值

    接下来,为 MyPromise 添加状态属性和值

    // 定义Promise的三种状态常量
    const PENDING = 'PENDING'
    const FULFILLED = 'FULFILLED'
    const REJECTED = 'REJECTED'
    

    再为 MyPromise 添加状态和值,并添加状态改变的执行逻辑

    class MyPromise {
      constructor (handle) {
        if (!isFunction(handle)) {
          throw new Error('MyPromise must accept a function as a parameter')
        }
        // 添加状态
        this._status = PENDING
        // 添加状态
        this._value = undefined
        // 执行handle
        try {
          handle(this._resolve.bind(this), this._reject.bind(this)) 
        } catch (err) {
          this._reject(err)
        }
      }
      // 添加resovle时执行的函数
      _resolve (val) {
        if (this._status !== PENDING) return
        this._status = FULFILLED
        this._value = val
      }
      // 添加reject时执行的函数
      _reject (err) { 
        if (this._status !== PENDING) return
        this._status = REJECTED
        this._value = err
      }
    }
    
    

    这样就实现了 Promise 状态和值的改变。下面说一说 Promise 的核心: then 方法

    1. Promise 的 then 方法

    Promise 对象的 then 方法接受两个参数:

    promise.then(onFulfilled, onRejected)
    
    参数可选

    onFulfilled 和 onRejected 都是可选参数。

    • 如果 onFulfilled 或 onRejected 不是函数,其必须被忽略
    onFulfilled 特性

    如果 onFulfilled 是函数

    • 当 promise 状态变为成功时必须被调用,其第一个参数为 promise 成功状态传入的值( resolve 执行时传入的值)
    • 在 promise 状态改变前其不可被调用
    • 其调用次数不可超过一次

    onRejected 特性

    如果 onRejected 是函数:

    • 当 promise 状态变为失败时必须被调用,其第一个参数为 promise 失败状态传入的值( reject 执行时传入的值)
    • 在 promise 状态改变前其不可被调用
    • 其调用次数不可超过一次
    多次调用

    then 方法可以被同一个 promise 对象调用多次

    • 当 promise 成功状态时,所有 onFulfilled 需按照其注册顺序依次回调
    • 当 promise 失败状态时,所有 onRejected 需按照其注册顺序依次回调
    返回

    then 方法必须返回一个新的 promise 对象

    promise2 = promise1.then(onFulfilled, onRejected);
    

    因此 promise 支持链式调用

    promise1.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2);
    

    Promise 的执行规则,包括“值的传递”和“错误捕获”机制:
    1、如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)

    • 若 x 不为 Promise ,则使 x 直接作为新返回的 Promise 对象的值, 即新的onFulfilled 或者 onRejected 函数的参数.
    • 若 x 为 Promise ,这时后一个回调函数,就会等待该 Promise 对象(即 x )的状态发生变化,才会被调用,并且新的 Promise 状态和 x 的状态相同。
    let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
      }, 1000)
    })
    promise2 = promise1.then(res => {
      // 返回一个普通值
      return '这里返回一个普通值'
    })
    promise2.then(res => {
      console.log(res) //1秒后打印出:这里返回一个普通值
    })
    
    let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
      }, 1000)
    })
    promise2 = promise1.then(res => {
      // 返回一个Promise对象
      return new Promise((resolve, reject) => {
        setTimeout(() => {
         resolve('这里返回一个Promise')
        }, 2000)
      })
    })
    promise2.then(res => {
      console.log(res) //3秒后打印出:这里返回一个Promise
    })
    

    2、如果 onFulfilled 或者onRejected 抛出一个异常 e ,则 promise2 必须变为失败(Rejected),并返回失败的值 e

    let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      }, 1000)
    })
    promise2 = promise1.then(res => {
      throw new Error('这里抛出一个异常e')
    })
    promise2.then(res => {
      console.log(res)
    }, err => {
      console.log(err) //1秒后打印出:这里抛出一个异常e
    })
    

    3、如果onFulfilled 不是函数且 promise1 状态为成功(Fulfilled), promise2 必须变为成功(Fulfilled)并返回 promise1 成功的值,例如:

    let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      }, 1000)
    })
    promise2 = promise1.then('这里的onFulfilled本来是一个函数,但现在不是')
    promise2.then(res => {
      console.log(res) // 1秒后打印出:success
    }, err => {
      console.log(err)
    })
    

    4、如果 onRejected 不是函数且 promise1 状态为失败(Rejected),promise2必须变为失败(Rejected) 并返回 promise1 失败的值,例如:

    let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('fail')
      }, 1000)
    })
    promise2 = promise1.then(res => res, '这里的onRejected本来是一个函数,但现在不是')
    promise2.then(res => {
      console.log(res)
    }, err => {
      console.log(err)  // 1秒后打印出:fail
    })
    

    根据上面的规则,来 完善 MyPromise

    修改 constructor : 增加执行队列

    由于 then 方法支持多次调用,维护两个数组,将每次 then 方法注册时的回调函数添加到数组中,等待执行

    constructor (handle) {
      if (!isFunction(handle)) {
        throw new Error('MyPromise must accept a function as a parameter')
      }
      // 添加状态
      this._status = PENDING
      // 添加状态
      this._value = undefined
      // 添加成功回调函数队列
      this._fulfilledQueues = []
      // 添加失败回调函数队列
      this._rejectedQueues = []
      // 执行handle
      try {
        handle(this._resolve.bind(this), this._reject.bind(this)) 
      } catch (err) {
        this._reject(err)
      }
    }
    

    添加then方法

    首先,then 返回一个新的 Promise 对象,并且需要将回调函数加入到执行队列中

    // 添加then方法
    then (onFulfilled, onRejected) {
      const { _value, _status } = this
      switch (_status) {
        // 当状态为pending时,将then方法回调函数加入执行队列等待执行
        case PENDING:
          this._fulfilledQueues.push(onFulfilled)
          this._rejectedQueues.push(onRejected)
          break
        // 当状态已经改变时,立即执行对应的回调函数
        case FULFILLED:
          onFulfilled(_value)
          break
        case REJECTED:
          onRejected(_value)
          break
      }
      // 返回一个新的Promise对象
      return new MyPromise((onFulfilledNext, onRejectedNext) => {
      })
    }
    

    那返回的新的 Promise 对象什么时候改变状态?改变为哪种状态呢?

    根据then 方法的规则,我们知道返回的新的 Promise 对象的状态依赖于当前 then 方法回调函数执行的情况以及返回值,例如 then 的参数是否为一个函数、回调函数执行是否出错、返回值是否为 Promise 对象。

    进一步完善 then 方法:

    // 添加then方法
    then (onFulfilled, onRejected) {
      const { _value, _status } = this
      // 返回一个新的Promise对象
      return new MyPromise((onFulfilledNext, onRejectedNext) => {
        // 封装一个成功时执行的函数
        let fulfilled = value => {
          try {
            if (!isFunction(onFulfilled)) {
              onFulfilledNext(value)
            } else {
              let res =  onFulfilled(value);
              if (res instanceof MyPromise) {
                // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                res.then(onFulfilledNext, onRejectedNext)
              } else {
                //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
                onFulfilledNext(res)
              }
            }
          } catch (err) {
            // 如果函数执行出错,新的Promise对象的状态为失败
            onRejectedNext(err)
          }
        }
        // 封装一个失败时执行的函数
        let rejected = error => {
          try {
            if (!isFunction(onRejected)) {
              onRejectedNext(error)
            } else {
                let res = onRejected(error);
                if (res instanceof MyPromise) {
                  // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                  res.then(onFulfilledNext, onRejectedNext)
                } else {
                  //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
                  onFulfilledNext(res)
                }
            }
          } catch (err) {
            // 如果函数执行出错,新的Promise对象的状态为失败
            onRejectedNext(err)
          }
        }
        switch (_status) {
          // 当状态为pending时,将then方法回调函数加入执行队列等待执行
          case PENDING:
            this._fulfilledQueues.push(fulfilled)
            this._rejectedQueues.push(rejected)
            break
          // 当状态已经改变时,立即执行对应的回调函数
          case FULFILLED:
            fulfilled(_value)
            break
          case REJECTED:
            rejected(_value)
            break
        }
      })
    }
    

    接着修改 _resolve 和 _reject :依次执行队列中的函数

    当 resolve 或 reject 方法执行时,我们依次提取成功或失败任务队列当中的函数开始执行,并清空队列,从而实现 then 方法的多次调用,实现的代码如下:

    // 添加resovle时执行的函数
    _resolve (val) {
      if (this._status !== PENDING) return
      // 依次执行成功队列中的函数,并清空队列
      const run = () => {
        this._status = FULFILLED
        this._value = val
        let cb;
        while (cb = this._fulfilledQueues.shift()) {
          cb(val)
        }
      }
      // 异步调用
      setTimeout(() => run(), 0)
    }
    // 添加reject时执行的函数
    _reject (err) { 
      if (this._status !== PENDING) return
      // 依次执行失败队列中的函数,并清空队列
      const run = () => {
        this._status = REJECTED
        this._value = err
        let cb;
        while (cb = this._rejectedQueues.shift()) {
          cb(err)
        }
      }
      // 异步调用
      setTimeout(run, 0)
    }
    

    这里还有一种特殊的情况,就是当 resolve 方法传入的参数为一个 Promise 对象时,则该 Promise 对象状态决定当前 Promise 对象的状态。

    const p1 = new Promise(function (resolve, reject) {
      // ...
    });
    
    const p2 = new Promise(function (resolve, reject) {
      // ...
      resolve(p1);
    })
    
    

    上面代码中,p1 和 p2 都是 Promise 的实例,但是 p2 的resolve方法将 p1 作为参数,即一个异步操作的结果是返回另一个异步操作。
    注意,这时 p1 的状态就会传递给 p2,也就是说,p1 的状态决定了 p2 的状态。如果 p1 的状态是Pending,那么 p2 的回调函数就会等待 p1 的状态改变;如果 p1 的状态已经是 Fulfilled 或者 Rejected,那么 p2 的回调函数将会立刻执行。
    我们来修改_resolve来支持这样的特性

    // 添加resovle时执行的函数
      _resolve (val) {
        const run = () => {
          if (this._status !== PENDING) return
          // 依次执行成功队列中的函数,并清空队列
          const runFulfilled = (value) => {
            let cb;
            while (cb = this._fulfilledQueues.shift()) {
              cb(value)
            }
          }
          // 依次执行失败队列中的函数,并清空队列
          const runRejected = (error) => {
            let cb;
            while (cb = this._rejectedQueues.shift()) {
              cb(error)
            }
          }
          /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
            当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
          */
          if (val instanceof MyPromise) {
            val.then(value => {
              this._value = value
              this._status = FULFILLED
              runFulfilled(value)
            }, err => {
              this._value = err
              this._status = REJECTED
              runRejected(err)
            })
          } else {
            this._value = val
            this._status = FULFILLED
            runFulfilled(val)
          }
        }
        // 异步调用
        setTimeout(run, 0)
      }
    

    这样一个Promise就基本实现了,现在来加一些其它的方法

    catch 方法

    相当于调用 then 方法, 但只传入 Rejected 状态的回调函数

    // 添加catch方法
    catch (onRejected) {
      return this.then(undefined, onRejected)
    }
    

    静态 resolve 方法

    // 添加静态resolve方法
    static resolve (value) {
      // 如果参数是MyPromise实例,直接返回这个实例
      if (value instanceof MyPromise) return value
      return new MyPromise(resolve => resolve(value))
    }
    

    静态 reject 方法

    // 添加静态reject方法
    static reject (value) {
      return new MyPromise((resolve ,reject) => reject(value))
    }
    

    静态 all 方法

    // 添加静态all方法
    static all (list) {
      return new MyPromise((resolve, reject) => {
        /**
         * 返回值的集合
         */
        let values = []
        let count = 0
        for (let [i, p] of list.entries()) {
          // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
          this.resolve(p).then(res => {
            values[i] = res
            count++
            // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
            if (count === list.length) resolve(values)
          }, err => {
            // 有一个被rejected时返回的MyPromise状态就变成rejected
            reject(err)
          })
        }
      })
    }
    

    静态 race 方法

    // 添加静态race方法
    static race (list) {
      return new MyPromise((resolve, reject) => {
        for (let p of list) {
          // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
          this.resolve(p).then(res => {
            resolve(res)
          }, err => {
            reject(err)
          })
        }
      })
    }
    

    finally 方法

    finally (cb) {
      return this.then(
        value  => MyPromise.resolve(cb()).then(() => value),
        reason => MyPromise.resolve(cb()).then(() => { throw reason })
      );
    };
    

    完整代码

     // 判断变量否为function
      const isFunction = variable => typeof variable === 'function'
      // 定义Promise的三种状态常量
      const PENDING = 'PENDING'
      const FULFILLED = 'FULFILLED'
      const REJECTED = 'REJECTED'
    
      class MyPromise {
        constructor (handle) {
          if (!isFunction(handle)) {
            throw new Error('MyPromise must accept a function as a parameter')
          }
          // 添加状态
          this._status = PENDING
          // 添加状态
          this._value = undefined
          // 添加成功回调函数队列
          this._fulfilledQueues = []
          // 添加失败回调函数队列
          this._rejectedQueues = []
          // 执行handle
          try {
            handle(this._resolve.bind(this), this._reject.bind(this)) 
          } catch (err) {
            this._reject(err)
          }
        }
        // 添加resovle时执行的函数
        _resolve (val) {
          const run = () => {
            if (this._status !== PENDING) return
            // 依次执行成功队列中的函数,并清空队列
            const runFulfilled = (value) => {
              let cb;
              while (cb = this._fulfilledQueues.shift()) {
                cb(value)
              }
            }
            // 依次执行失败队列中的函数,并清空队列
            const runRejected = (error) => {
              let cb;
              while (cb = this._rejectedQueues.shift()) {
                cb(error)
              }
            }
            /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
              当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
            */
            if (val instanceof MyPromise) {
              val.then(value => {
                this._value = value
                this._status = FULFILLED
                runFulfilled(value)
              }, err => {
                this._value = err
                this._status = REJECTED
                runRejected(err)
              })
            } else {
              this._value = val
              this._status = FULFILLED
              runFulfilled(val)
            }
          }
          // 异步调用
          setTimeout(run, 0)
        }
        // 添加reject时执行的函数
        _reject (err) { 
          if (this._status !== PENDING) return
          // 依次执行失败队列中的函数,并清空队列
          const run = () => {
            this._status = REJECTED
            this._value = err
            let cb;
            while (cb = this._rejectedQueues.shift()) {
              cb(err)
            }
          }
          // 异步调用
          setTimeout(run, 0)
        }
        // 添加then方法
        then (onFulfilled, onRejected) {
          const { _value, _status } = this
          // 返回一个新的Promise对象
          return new MyPromise((onFulfilledNext, onRejectedNext) => {
            // 封装一个成功时执行的函数
            let fulfilled = value => {
              try {
                if (!isFunction(onFulfilled)) {
                  onFulfilledNext(value)
                } else {
                  let res =  onFulfilled(value);
                  if (res instanceof MyPromise) {
                    // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                    res.then(onFulfilledNext, onRejectedNext)
                  } else {
                    //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
                    onFulfilledNext(res)
                  }
                }
              } catch (err) {
                // 如果函数执行出错,新的Promise对象的状态为失败
                onRejectedNext(err)
              }
            }
            // 封装一个失败时执行的函数
            let rejected = error => {
              try {
                if (!isFunction(onRejected)) {
                  onRejectedNext(error)
                } else {
                    let res = onRejected(error);
                    if (res instanceof MyPromise) {
                      // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                      res.then(onFulfilledNext, onRejectedNext)
                    } else {
                      //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
                      onFulfilledNext(res)
                    }
                }
              } catch (err) {
                // 如果函数执行出错,新的Promise对象的状态为失败
                onRejectedNext(err)
              }
            }
            switch (_status) {
              // 当状态为pending时,将then方法回调函数加入执行队列等待执行
              case PENDING:
                this._fulfilledQueues.push(fulfilled)
                this._rejectedQueues.push(rejected)
                break
              // 当状态已经改变时,立即执行对应的回调函数
              case FULFILLED:
                fulfilled(_value)
                break
              case REJECTED:
                rejected(_value)
                break
            }
          })
        }
        // 添加catch方法
        catch (onRejected) {
          return this.then(undefined, onRejected)
        }
        // 添加静态resolve方法
        static resolve (value) {
          // 如果参数是MyPromise实例,直接返回这个实例
          if (value instanceof MyPromise) return value
          return new MyPromise(resolve => resolve(value))
        }
        // 添加静态reject方法
        static reject (value) {
          return new MyPromise((resolve ,reject) => reject(value))
        }
        // 添加静态all方法
        static all (list) {
          return new MyPromise((resolve, reject) => {
            /**
             * 返回值的集合
             */
            let values = []
            let count = 0
            for (let [i, p] of list.entries()) {
              // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
              this.resolve(p).then(res => {
                values[i] = res
                count++
                // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
                if (count === list.length) resolve(values)
              }, err => {
                // 有一个被rejected时返回的MyPromise状态就变成rejected
                reject(err)
              })
            }
          })
        }
        // 添加静态race方法
        static race (list) {
          return new MyPromise((resolve, reject) => {
            for (let p of list) {
              // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
              this.resolve(p).then(res => {
                resolve(res)
              }, err => {
                reject(err)
              })
            }
          })
        }
        finally (cb) {
          return this.then(
            value  => MyPromise.resolve(cb()).then(() => value),
            reason => MyPromise.resolve(cb()).then(() => { throw reason })
          );
        }
      }
    
    
  • 相关阅读:
    [ERR] Node 10.211.55.8:7001 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.
    PAT A1137 Final Grading (25 分)——排序
    PAT A1136 A Delayed Palindrome (20 分)——回文,大整数
    PAT A1134 Vertex Cover (25 分)——图遍历
    PAT A1133 Splitting A Linked List (25 分)——链表
    PAT A1132 Cut Integer (20 分)——数学题
    PAT A1130 Infix Expression (25 分)——中序遍历
    PAT A1142 Maximal Clique (25 分)——图
    PAT A1141 PAT Ranking of Institutions (25 分)——排序,结构体初始化
    PAT A1140 Look-and-say Sequence (20 分)——数学题
  • 原文地址:https://www.cnblogs.com/yangAL/p/14583772.html
Copyright © 2011-2022 走看看