1. Promise含义
所谓Promise,简单来说就是一个容器,里面保存着某个未来的才会结束的事件(通常是一个异步操作的结果。从语法上说,Promise是一个对象,从它可以异步获取操作的消息。
1.1 Promise两个特点
- 对象状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfillde(已成功)、rejected(已失败)。
- 状态一旦改变,就不会在改变。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
1.2 Promise优点
- Promise对象,可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
- Promise对象提供统一的接口,使得控制异步操作更加容易。
1.3 Promise缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
2. promise基本用法
2.1 resolve和reject
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
2.2 race
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
2.3 all
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。
-
只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
-
只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
2.4 allSettled
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
2.5 any
该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
3. EventLoop
EventLoop是一个程序结构,用于等待和发送消息和事件。简单说,就是在程序中设置两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为"Event Loop线程"(可以译为"消息线程")。
大体解释一下NodeJS的Event Loop过程:
- 执行全局Script的同步代码
- 执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务
- 开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,注意,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2
3.1 宏队列
宏队列,macrotask,也叫tasks。 一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:
- setTimeout
- setInterval
- setImmediate (Node独有)
- requestAnimationFrame (浏览器独有)
- I/O
- UI rendering (浏览器独有)
NodeJS的Event Loop中,执行宏队列的回调任务有6个阶段,如下图:
各个阶段执行的任务如下:
- timers阶段:这个阶段执行setTimeout和setInterval预定的callback
- I/O callback阶段:执行除了close事件的callbacks、被timers设定的callbacks、setImmediate()设定的callbacks这些之外的callbacks
- idle, prepare阶段:仅node内部使用
- poll阶段:获取新的I/O事件,适当的条件下node将阻塞在这里
- check阶段:执行setImmediate()设定的callbacks
- close callbacks阶段:执行socket.on('close', ....)这些callbacks
NodeJS中宏队列主要有4个
- Timers Queue
- IO Callbacks Queue
- Check Queue
- Close Callbacks Queue 这4个都属于宏队列,但是在浏览器中,可以认为只有一个宏队列,所有的macrotask都会被加到这一个宏队列中,但是在NodeJS中,不同的macrotask会被放置在不同的宏队列中。
3.2 微队列
微队列,microtask,也叫jobs。 另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:
- process.nextTick (Node独有)
- Promise
- Object.observe
- MutationObserver
NodeJS中微队列主要有2个:
- Next Tick Queue:是放置process.nextTick(callback)的回调任务的
- Other Micro Queue:放置其他microtask,比如Promise等 在浏览器中,也可以认为只有一个微队列,所有的microtask都会被加到这一个微队列中,但是在NodeJS中,不同的microtask会被放置在不同的微队列中。
4.Promise A+规范
4.1 Promise的声明
class声明,由于 new Promise((resolve,reject) =>{}) , 所以传入一个参数(函数) ,PromiseA+里叫他executor(作业执行器),传入就执行。
class Promise {
// 构造器
constructor(executor) {
// 成功
let resolve = () => {};
// 失败
let reject = () => {};
// 立即执行
executor(resolve, reject);
}
}
4.2 解决基本状态
PromiseA+对Promise有规定:
new Promise((resolve, reject)=>{resolve(value)})
resolve为成功,接收参数value,状态改变为fulfilled,不可再次改变。
new Promise((resolve, reject)=>{reject(reason)})
reject为失败,接收参数reason,状态改变为rejected,不可再次改变。
若是executor函数报错 直接执行reject();
于是我们获得以下代码
class Promise {
// 构造器
constructor(executor) {
// 初始化state为等待态
this.state = 'pending'
// 成功的值
this.value = undefined
// 失败的原因
this.reason = undefined
// 成功
let resolve = value => {
// state改变,resolve调用就会失败
if (this.state === 'pending') {
// resolve调用后,state转化为成功态
this.state = 'fulfilled'
// 储存成功的值
this.value = value
}
}
// 失败
let reject = reason => {
// state改变,reject调用就会失败
if (this.state === 'pending') {
// reject调用后,state转化为失败态
this.state = 'rejected'
// 储存失败的原因
this.reason = reason
}
}
// 如果executor执行报错,直接执行reject
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
}
4.3 then方法
PromiseA+规定:Promise有一个叫做then的方法,里面有两个参数:onFulfilled,onRejected,成功有成功的值,失败有失败的原因
当状态state为fulfilled,则执行onFulfilled,传入this.value。当状态state为rejected,则执行onRejected,传入this.reason
onFulfilled,onRejected如果他们是函数,则必须分别在fulfilled,rejected后被调用,value或reason依分别为他们的第一个参数
class Promise {
// 构造器
constructor(executor) {...}
// then方法 有两个参数 onFulfilled onRejected
then(onFulfilled, onRejected) {
// 状态为fulfilled,执行onFulfilled,传入成功的值
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
if (this.state === 'rejected') {
onRejected(this.reason)
}
}
}
4.4 解决异步实现
当resolve在setTimeout内执行,then时state还是pending等待状态 我们就需要在then调用的时候,将成功和失败存到各自的数组,一旦reject或者resolve,就调用它们
类似于发布订阅,先将then里面的两个函数储存起来,由于一个Promise可以有多个then,所以存在同一个数组内。
//executor 执行器的实现
//状态转移
//thenable
//如何让执行器决定状态转移的执行
class Promise{
constructor(executor){
this.state = 'pending' //初始化未完成状态
//成功的值
this.value = undefined;
//失败的原因
this.reason = undefined;
//异步任务会把结果交给resolve
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = []
let resolve = (value) =>{
// console.log("fullfilled状态被执行")
if(this.state == "pending"){
this.value = value
this.state = "fulfilled"
this.onResolvedCallbacks.forEach(fn =>fn())
}
console.log("fulfilled状态被执行",this.value,this.state)
//onFulfilled执行一下 记忆功能
}
let reject = (reason) =>{
// console.log("reject状态被执行")
if(this.state =="pending"){
this.reason = reason
this.state = "rejected"
this.onRejectedCallbacks.forEach(fn =>fn())
}
console.log("reject状态被执行",this.reason,this.state)
}
// executor(resolve,reject)
try{
executor(resolve,reject)
}catch(e){
reject(err)
}
}
//当前promise 解决了 完成了状态转移 把控制权交出来
then(onFulfilled,onRejected){
console.log('then ........')
//如果状态为fulfilled 时 , 传入成功后的回调 将执行权转移
if(this.state == 'fulfilled'){
console.log('onFulfilled,----')
onFulfilled(this.value);
}
//状态为rejected 传入为失败后的回调
if(this.state == 'rejected'){
onRejected(this.reason)
}
if(this.state == 'pending'){
this.onResolvedCallbacks.push(()=>{
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(()=>{
onRejected(this.reason)
})
}
}
// then(){
// }
}
new Promise((resolve, reject) => {
// 将花时间比较多的任务封装起来, 以实现异步变同步
setTimeout(() => {
console.log(0)
resolve(10);
// throw new Error('出错了')
}, 1000)
}).then((data)=>{
console.log(data,"++++++");
})
4.5解决链式调用
PromiseA+规定了一种方法,就是在then里面返回一个新的promise,称为promise2: promise2 = new Promise((resolve, reject)=>{})
将这个promise2返回的值传递到下一个then中
如果返回一个普通的值,则将普通的值传递给下一个then中
PromiseA+则规定onFulfilled()或onRejected()的值,即第一个then返回的值,叫做x,判断x的函数叫做resolvePromise
// MyPromise.js
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 MyPromise 类
class MyPromise {
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject)
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// resolve和reject为什么要用箭头函数?
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 用箭头函数就可以让this指向当前实例对象
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
onFulfilledCallbacks = [];
// 存储失败回调函数
// onRejectedCallback = null;
onRejectedCallbacks = [];
// 更改成功后的状态
// MyPromise.js
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// ==== 新增 ====
// resolve里面将所有成功的回调拿出来执行
// console.log(this.onFulfilledCallbacks)
while (this.onFulfilledCallbacks.length) {
// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
this.onFulfilledCallbacks.shift()(value)
}
}
}
// MyPromise.js
// 更改失败后的状态
// MyPromise.js
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// ==== 新增 ====
// resolve里面将所有失败的回调拿出来执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
then(onFulfilled, onRejected) {
// ==== 新增 ====
// 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
const promise2 = new MyPromise((resolve, reject) => {
// 这里的内容在执行器中,会立即执行
if (this.status === FULFILLED) {
// 获取成功回调函数的执行结果
const x = onFulfilled(this.value);
// 传入 resolvePromise 集中处理
resolvePromise(x, resolve, reject);
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
})
return promise2;
}
}
function resolvePromise(x, resolve, reject) {
// 判断x是不是 MyPromise 实例对象
if(x instanceof MyPromise) {
// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
// x.then(value => resolve(value), reason => reject(reason))
// 简化之后
x.then(resolve, reject)
} else{
// 普通值
resolve(x)
}
}
// test.js
const promise = new MyPromise((resolve, reject) => {
// 目前这里只处理同步的问题
resolve('success')
})
function other () {
return new MyPromise((resolve, reject) =>{
resolve('other')
})
}
promise.then(value => {
console.log(1)
console.log('resolve', value)
return other()
}).then(value => {
console.log(2)
console.log('resolve', value)
})
5. 回调地狱
5.1 什么是回调地狱
- 多层嵌套问题
- 每种任务的处理结果存在两种可能性(成功或失败) , 那么需要在每种任务执行结束后分别处理这两种可能性
5.2 解决方法
Promise利用三大技术来解决回调地狱
5.2.1 回调函数延迟绑定
通过后面的 then 方法传入的,即延迟传入。这就是回调函数延迟绑定。
let readFilePromise = (filename) => {
fs.readFile(filename, (err, data) => {
if(err) {
reject(err);
}else {
resolve(data);
}
})
}
readFilePromise('1.json').then(data => {
return readFilePromise('2.json')
});
5.2.2 返回值穿透
我们会根据 then 中回调函数的传入值创建不同类型的Promise, 然后把返回的 Promise 穿透到外层, 以供后续的调用。这里的 x 指的就是内部返回的 Promise,然后在 x 后面可以依次完成链式调用。这便是返回值穿透的效果。
let x = readFilePromise('1.json').then(data => {
return readFilePromise('2.json')//这是返回的Promise
});
x.then(/* 内部逻辑省略 */)
5.2.3 错误冒泡
这样前面产生的错误会一直向后传递,被 catch 接收到,就不用频繁地检查错误了。
readFilePromise('1.json').then(data => {
return readFilePromise('2.json');
}).then(data => {
return readFilePromise('3.json');
}).then(data => {
return readFilePromise('4.json');
}).catch(err => {
// xxx
})
5.3 解决效果
- 实现链式调用,解决多层嵌套问题
- 实现错误冒泡后一站式处理,解决每次任务中判断错误、增加代码混乱度
6. then方法
6.1 then方法识别调用promise
如果 then 方法返回的是自己的 Promise 对象,则会发生循环调用,这个时候程序会报错
// test.js
const promise = new Promise((resolve, reject) => {
resolve(100)
})
const p1 = promise.then(value => {
console.log(value)
return p1
})
6.2 捕获错误
6.2.1 捕获执行器错误
constructor(executor){
// ==== 新增 ====
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
try {
executor(this.resolve, this.reject)
} catch (error) {
// 如果有错误,就直接执行 reject
this.reject(error)
}
}
6.2.2 then执行时错误捕获
then(onFulfilled, onRejected) {
// 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
const promise2 = new MyPromise((resolve, reject) => {
// 判断状态
if (this.status === FULFILLED) {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
// ==== 新增 ====
try {
// 获取成功回调函数的执行结果
const x = onFulfilled(this.value);
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
} else if (this.status === PENDING) {
// 等待
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到执行成功失败函数的时候再传递
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
})
return promise2;
}
6.3 then参数可传可不传
我们处理 then 方法的时候都是默认传入 onFulfilled、onRejected 两个回调函数,但是实际上原生 Promise 是可以选择参数的单传或者不传,都不会影响执行。
then(onFulfilled, onRejected) {
// 如果不传,就使用默认函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
}
参考链接:
blog.csdn.net/qq_43540219…
http://47.98.159.95/my_blog/blogs/javascript/js-async/004.html
juejin.cn/post/694531…
zhuanlan.zhihu.com/p/55511602