顺序执行?

很多刚开始写JS代码的同学觉得JS是顺序执行:

var aaa = function () {

    console.log('aaa');

}

aaa();  // aaa

var bbb = function () {

    console.log('bbb');

}

bbb(); // bbb

然而去看这段代码:

function aaa() {

    console.log('aaa');

}

aaa();  // bbb

function bbb() {

    console.log('bbb');

}

bbb(); // bbb

打印出两个bbb。有一定经验的开发人员知道 JS 并非一行一行地分析和执行程序。

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引擎线程空闲,系统会读取任务队列,将可运行的异步任务回调事件添加到执行栈中,开始执行 

###宏任务 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 最后输出

页面的背景色直接变成黑色,没有经过蓝色的阶段,是因为,我们在宏任务中将背景设置为蓝色,但在进行渲染前执行了微任务,

在微任务中将背景变成了黑色,然后才执行的渲染

首先,整体的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')

    }

  }

}

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 方法

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 })

      );

    }

  }

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

热门产品

php编程基础教程.pptx|php编程培训,php,编程,基础,教程,pptx
php编程基础教程.pptx

历史上的今天:04月28日

referrer策略和meta标签的问题

referrer策略和meta标签的问题请求后端接口时,banner图片的请求出现403错误:GEThttp://xxxxxxxxxxxx403(Forbidden)。在网上搜寻一番,解决方法如下:在index.html中的head中添加&lt;meta name=&quot;referrer&quot; content=&quot;no-referrer&quot; /&gt;。在此之前,关于r

热门专题

昆明综合高中|昆明综合高中
昆明综合高中
小程序开发|微信小程序,小程序开发,小程序,小程序制作,微信小程序开发,小程序公司,小程序开发公司,分销,三级分销系统,分销系统
小程序开发
中源管业|中源管业,中源管业公司,中源管业有限公司,中源管业电话,中源管业地址,中源管业电力管,中源管业mpp电力管,中源管业cpvc电力管,中源管业pe穿线管
中源管业
云南综合高中|云南综合高中
云南综合高中
易捷尔单招|易捷尔单招,易捷尔单招培训,易捷尔单招报名,易捷尔单招考试,易捷尔单招培训学校,易捷尔单招分数
易捷尔单招
云南巨榕教育投资集团有限公司|云南巨榕教育投资集团有限公司,巨榕教育集团,巨榕教育
云南巨榕教育投资集团有限公司
开放大学|开放大学报名,开放大学报考,开放大学,什么是开放大学,开放大学学历,开放大学学费,开放大学报名条件,开放大学报名时间,开放大学学历,开放大学专业
开放大学
云南高职单招|云南单招,云南单招网,云南高职单招网,云南高职单招,云南单招学校,云南单招培训
云南高职单招

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部