Promise
状态转移
three states, Pending, Fullfilled, Rejected.
- Initialization and Pending state(promise是定义立即执行的)
- 根据事件机制执行resolve() or reject()
- 执行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为对象或者函数
- 把x.then赋值给then(大概率是undefinied)
- 如果取x.then的值时抛出错误e,那么e就是拒因
- 如果then是函数,将x作为函数的作用域this调用该函数,传递2个回调函数作为参数,resolvePromise和rejectPromise
- 以第一个运行的回调函数决定promise的状态,即使resolvePromise之后throw error,也不会改变onFulfilled的状态
- 若resolvePromise以y为参数被调用,则运行
[[Resolve]](promise, y) - 如果rejectPromise以r为reason或者throw r,以r为拒因拒绝promise
- 如果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阶段控制,