事件轮询/PROMISE(未完成)

2,173 阅读7分钟

Promise

状态转移

three states, Pending, Fullfilled, Rejected.

  1. Initialization and Pending state(promise是定义立即执行的)
  2. 根据事件机制执行resolve() or reject()
  3. 执行then注册回调处理函数(then 可被同一个promise多次调用)

Promise要保证then里面注册的onFulfilled 和 onRejected 必须在then方法被调用的那一轮事件循环之后的新执行栈中执行。

Promise在JS的事件轮询中属于micro task / jobs 会在当前macro task执行完毕后执行队列里面的micro task。then就相当于把onFulfilled/onRejected 注册到 micro task queue里面。 若marco task 不为空 ,then注册的jobs需要紧跟在下一轮marco task中。

真正的链式Promise是指在当前promise达到fulfilled状态后,即开始进行下一个promise.

如何理解? 立即开始下一个promise,说明marco task queue是空的?

链式调用

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve({ test: 1 })
        resolve({ test: 2 })
        reject({ test: 2 })
    }, 1000)
}).then((data) => {
    console.log('result1', data)
},(data1)=>{
    console.log('result2',data1)
}).then((data) => {
    console.log('result3', data)
})
//result1 { test: 1 }
//result3 undefined

promise是不可逆的,因此第一次resolve就决定了第一个promise的状态,转移到第一个then中的onFulfilled, 输出result1, 因为成功运行没有reject,转移到第二个then,由于第一个then没有给第二个then传入数据,所以是undefined。

手写promise

理解Promise A+ 规范

术语

  • fulfill/resolve 解决,表示一个promise成功时进行的一系列操作,状态的改变,回调是执行。
  • reject promise失败时进行的一系列操作
  • eventual value 终值,指promise被解决时传递给解决回调的值,由于promise有一次性,不可逆的特征,因此当这个值被传递时,表示promise等待态的结束
  • reason,拒绝原因,指在promise被拒绝时传递给拒绝回调的值
  • Promise,一个拥有then方法的对象或者函数并且行为符合此规范
  • thenable,一个有then方法的对象或者函数

状态标准

  • Pending: 可以迁移到Fulfilled或者Rejected
  • FulFIlled 不能迁移到其他任何状态,有一个不可变的eventual value,
  • Rejected 不能迁移到其他任何状态,有一个不可变的reason

这里的不可变是===相等,value/reason 的地址不可变 属性可以

Then方法

Promise必须提供一个then方法以访问当前值,终值和拒因 then方法接收两个参数,(onFulfilled,onRejected) 这两个参数必须都是可选参数,而且不是函数的情况下必须忽略。因此这两个参数必须作为函数被调用(没有this值,不指向任何一个obj)

onFulfilled
  • 当promise执行结束后必须被调用,第一个参数为promise的value
  • 当promise执行结束前 其不可被调用
  • 调用次数不超过1
onRejected
  • 当promise拒绝执行后必须被调用,第一个参数为promise的reason
  • 当promise拒绝执行前 其不可被调用
  • 调用次数不超过1
调用时机

onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用

调用要求

onFulfilled 和 onRejected 必须被作为函数调用(即没有 this 值)

多次调用
  • then 方法可以被同一个 promise 调用多次当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
  • 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调

返回Promise

then方法必须返回一个promise

promise2 = promise1.then(onFulfilled, onRejected);   
  • 如果onFulfilled, onReject都成功指向,promise2必须执行resolve
  • 如果onFulfilled,onReject throw error promise2必须拒绝执行
  • 即使这两个参数不是函数,成功执行promise2---resolve并返回相应的值 失败/error --- promise2必须拒绝执行

关键在于onFulfilled和onReject都不影响是否执行promise2的resolve,真正决定promise2执行哪个函数的是promise1是否成功执行,有没有抛出错误

Promise解决过程

Promise解决过程输入一个promise和一个值,[[Resolve]] (promise,x) 如果x有then的方法且看上去像一个promise,解决程序尝试使promise接收x的状态,否则用x的值来执行promise。 执行解决过程有以下几种状态

x与promise相等

若x===promise,TypeError为拒因拒绝执行promise

x为Promise

若x是promise,使promise接收x的状态

  • pending,则promise保持pending直到x状态改变
  • fulfill, promise用相同的值执行
  • reject,promise用相同的拒因
x为对象或者函数
  1. 把x.then赋值给then(大概率是undefinied)
  2. 如果取x.then的值时抛出错误e,那么e就是拒因
  3. 如果then是函数,将x作为函数的作用域this调用该函数,传递2个回调函数作为参数,resolvePromise和rejectPromise
  • 以第一个运行的回调函数决定promise的状态,即使resolvePromise之后throw error,也不会改变onFulfilled的状态
  • 若resolvePromise以y为参数被调用,则运行[[Resolve]](promise, y)
  • 如果rejectPromise以r为reason或者throw r,以r为拒因拒绝promise
  1. 如果x不是对象或者函数,以x为参数执行promise

总结

  • promise立即执行 不论是定义还是赋值
  • catch能捕捉上层的错误,但是catch本身return error不能捕捉
  • 可以使用resolve,reject函数直接改变状态,如果reject,then是捕捉不到数据的,会出错
  • then,catch都会返回一个promise,因此then里面return Error 会被当成字符串变量
  • 要合法抛出错误,要使用 Promise.reject(new Error('error') throw new Error('error')
  • 如果then里面return promise 是将要赋值的同一个promise,那么会进入死循环报错
  • then只能接收函数作为参数,非函数会发生值穿透,将最开始的值 依次传递下去。
  • 一个错误只能被拦截一次,除非又发生了错误
  • finally,的回调函数不接受任何参数,return只能是上个promise的值,抛出错误只能是使用throw

all && race

async/await

和promise不同的地方在于,async不会立即执行,而且在async内部使用await会移交程序控制权,不会继续执行。await后面的程序,就相当于放到了Promise.then后面。

但是定时器setTimeOut作为marco task,then作为micro任务,阻塞的优先级需要搞清楚。 await 后面的如果是promise,且不从pending中跳出来,那么await后面的代码是不会运行的 async中抛出了错误,会中止执行

易错点

  • 定时间会影响marco task执行顺序
  • finally接收不到primoise的值
  • await 后面的promise若keep pending 不会执行后续

练习题

使用promise每隔1s输出1,2,3

使用array reduce promise。 注意利用promise的pending状态控制间隔,以及什么时候应该resolve

event-loop

In JS, besides function stack, there are many other task queues keeping the executation order of the programs. In task queues, there are macro-tasks and micro-tasks, or tasks and jobs. Tasks:

  • script
  • setTimeout/setInterval/serImmediate
  • I/O
  • UI render

JOBS

  • promise
  • Async/Await
  • process.nextTick
  • MutationObserver

在执行下个tasks之前会一直执行jobs和jobs衍生出来的jobs,下面代码的执行顺序?

async function async1(){
  await async2();
  console.log('async1 end');
}
async function async2(){
  console.log('async2 end');
}
async1()
setTimeout(() => {
  console.log('setTimeout');
}, 0);
new Promise(resolve => {
  console.log('Promise')
  resolve();
})
.then(function(){
  console.log('promise1');
})
.then(function(){
  console.log('promise2');
})

答案输出为:async2 end => Promise => async1 end => promise1 => promise2 => setTimeout 但是,对于async/await

事件执行顺序

  • 一开始整个脚本作为一个marco task进行
  • 同步代码直接执行,marco/micro tasks 分别进入队列
  • 执行micro task queue 清空其
  • UI render
  • web worker task
  • 执行marco task 从第二步循环

Event Loop in Node

Node中的事件轮询和浏览器中页面的渲染完全不同,它们按照顺序执行,有六个阶段。当队列为空或者执行的回调函数达到阈值,进入下一阶段。

Timer

负责执行setTimeout, setInterval,由poll阶段控制,