ES6之前,以往的异步方法,是使用setTimeout嵌套处理代码,如何再嵌套setTimeout。
// setTimeout异步方式
setTimeout(() => {
const f1 = () => {
//...逻辑代码
return 1;
};
let res1 = f1();
setTimeout(() => {
const f2 = () => {
return res1 + 1;
};
let res2 = f2();
setTimeout(() => {
//使用res2值做操作。
// 。。。
}, 3000);
}, 3000);
}, 3000);
以上做了三层嵌套,会发现理解起来和维护会比较困难,所以ES6之后就增加了Promise规范和浏览器promiseAPI。
1、Promise基础
先介绍一个小知识点:
ECMASript变量值可以分为两种类型数据:原始值和引用值。
- 原始值:最简单的数据,对应的类型是JS的原始类型:Undefined、Null、Boolean、Number、String、Symbol。
例如:
const c = 0; //0为原始值, c的类型为原始类型number类型
- 引用值:由多个值构成的对象,是特定引用类型的实例,对应的类型是复杂类型(引用类型):Object,引用类型是把数据和功能组合在一起的结果,有点像类。
例如:
let Fn = {
a: 0,
fa() {
console.log(this.a);
},
};
//Fn为引用值,Fn的类型为对象类型Object的实例
class P {
a = 0;
fa() {
console.log(this.a);
}
}
let p = new P(); //p为引用值,引用类型为P
console.log(p.a); //0
p.fa(); //0
Promise是一个引用类型
,可以通过new操作符来实例化一个期约实例。new一个期约实例时必须得传一个执行器函数参数。否则会报一个类型错误
,
//不正确实例化
let p = new Promise(); //TypeError: Promise resolver undefined is not a function
let fe = () => {};
let p = new Promise(fe);
console.log("🚀 ~ file: promise.js ~ line 45 ~ p", p);
//🚀 ~ file: promise.js ~ line 45 ~ p Promise { <pending> }
上面表示期约实例的状态时pending,期约实例可能是一个有状态的对象,有三种:
- pending 待定
- resolved 解决
- rejected 拒绝
这三种状态取决于执行器函数fe,执行器函数fe可以接受两个函数参数,可以命名为resolve和reject,在fe中调用第一个函数参数会把resolve接受的参数作为结果异步返回出去并把期约状态变为resolved,调用第二个函数参数reject会把接受到的参数作为理由异步返出去并把状态改为reject,并抛出错误。fe中第一个第二个函数参数都不调用时,期约实例状态为pending。
//调用第一个参数
let p = new Promise((resolve, reject) => {
resolve(1);
});
console.log(p);//Promise {<fulfilled>: 1}
//调用第二个参数
let p = new Promise((resolve, reject) => {
reject(1);
});
console.log(p);
//Promise {<rejected>: 1}
//Uncaught (in promise) 1 //这个错误是在异步队列里的同步代码
//trycatch是抓取不到的。
let p = new Promise((resolve, reject) => {
try { //浏览器依旧报错
reject(1);
} catch (error) {}
});
console.log(p);
//Promise {<rejected>: 1}
//Uncaught (in promise) 1
//不调用第一第二参数
let p = new Promsie(()=>{});
console.log(p) //Promise {<pending>}
所以,期约实例是有状态的,期约实例p的状态只与执行器函数前两个函数参数是否执行有关。注意:执行器函数是同步执行代码
,例如:
let fe = () => {
console.log(10);
return 11;
};
let p = new Promise((resolve, reject) => {
console.log(0);
resolve(fe());
console.log(2);
});
console.log(3);
console.log(p);
// 0;
// 10;
// 2;
// 3;
//Promise { 11 }
而且一个期约实例的状态只会改变一次,就是调用了两次resolve或reject函数,只会受最先执行的那一个影响。例如:
//调用两次resolve
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 2000);
setTimeout(() => {
resolve(2);
}, 1000);
});
console.log(3);
setTimeout(console.log, 3000, p);
console.log(p);
// 3
// Promise { <pending> }
// Promise { 2 } //三秒之后打印。
//调用两次reject
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject(1);
}, 2000);
setTimeout(() => {
reject(2);
}, 1000);
});
console.log(3);
setTimeout(console.log, 3000, p);
console.log(p);
//3
//Promise {<pending>}
//Uncaught (in promise) 2 第二秒报错
//Promise {<rejected>: 2} 第三秒打印
以上是不调用参数函数状态为pending,调用第一个函数参数则有pending转为resolved,调用第二个参数则由pending转为rejected,并在异步里报错。
2、Promise的方法
Promise类型有自己的静态方法Promise.resolve()和Promise.reject()。
第一个方法Promise.resolve()实例化一个解决的期约。等价于:
let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();
console.log(p1); //Promise {<fulfilled>: undefined}
console.log(p2); //Promise {<fulfilled>: undefined}
因为resolve函数没有传参,所以解决值为undefined。第一个参数为解决值,多余的参数会被忽略。
setTimeout(console.log, 0, Promise.resolve(4, 5, 6));
// Promise <resolved>: 4
第二个方法Promise.reject()会实例化一个拒绝的期约并异步抛出一个错误。等价于:
let p1 = new Promise((resolve, reject) => reject());
let p2 = Promise.reject();
3、Promise实例的方法
前面将来期约实例是有三种状态的,ECMAScript设计实例几种方法可以根据状态来相应的异步执行。这些方法里的函数参数都是异步调用执行的。
Promise.prototype.then()方法
then()方法最多接受两个参数:onResolved 处理函数和 onRejected 处理函数,可选的,期约状态变为resolved时执行onResolved 处理函数,接受的参数为resolve返出的参数,期约状态变为rejected时执行onRejected 处理函数,接受的参数为reject返出的参数,并且会 抓取reject()报的错误。
then()方法的第一个参数为onResolved 处理函数,第二个参数为onRejected 处理函数。
例如:
//期约状态变为resolved时执行onResolved 处理函数
let p = new Promise((resolve, reject) => {
resolve(1);
// reject(1);
});
p.then(
(res) => { //这里执行
console.log(res);
console.log("resolve");
},
(reject) => {
console.log(reject);
console.log("reject");
}
);
//Promise {<fulfilled>: 1}
//1
//resolve
//期约状态变为rejected时执行onRejected 处理函数
let p = new Promise((resolve, reject) => {
resolve(1);
// reject(1);
});
p.then(
(res) => {
console.log(res);
console.log("resolve");
},
(reject) => { //这里执行
console.log(reject);
console.log("reject");
}
);
//Promise {<rejected>: 1}
//1
//reject
Promise.prototype.catch()方法
Promise.prototype.catch()方法用于给期约添加拒绝处理程序,这个方法只接收一个参数:onRejected 处理程序。调用它就相当于调用 Promise.prototype.then(null, onRejected);
let p = new Promise((resolve, reject) => {
// resolve(1);
reject(1);
});
console.log(p);
// p.then(
// (res) => {
// console.log(res);
// console.log("resolve");
// },
// (reject) => {
// console.log(reject);
// console.log("reject");
// }
// );
p.catch((reject) => {
console.log(reject);
console.log("reject");
});
//Promise {<rejected>: 1}
//1
//reject
Promise.prototype.finally()方法
Promise.prototype.finally()方法用于给期约添加 onFinally 处理程序,这个处理程序在期约转换为解决或拒绝状态时都会执行。这个方法主要是为解决onResolved 和 onRejected 处理程序冗余代码。只要给该方法添加了处理函数,期约实例状态只要是resolved或rejected,都会异步执行处理函数。但finally不会抓取reject()报的错误。
let p = new Promise((resolve, reject) => {
// resolve(1);
reject(1);
});
console.log(p);
p.then(
(res) => {
console.log(res);
console.log("resolve");
},
(reject) => {
console.log(reject);
console.log("reject");
}
);
p.finally((fin) => {
console.log(fin);
console.log("finally");
});
//Promise {<rejected>: 1}
//1
//reject
//undefined
//finally
//Promise {<rejected>: 1}
4、Promise连锁与合成
连锁即为串联,每个期约实例的方法(then()、catch()和 finally())都会返回一个新的期约对象
回到最初的setTimeout异步回调问题:
使用promise连锁方式解决:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 3000);
});
p.then((res) => {//这里异步执行并依赖执行器函数resolve什么时候调用
console.log(res);
return res + 1;
}).then((res) => {//这里也是异步执行并依赖什么时候前一个实例resolve
console.log(res);
});
Promise.all()
接受一组期约,返回的也是期约实例,等这组所有的期约都解决之后才解决。这一组一个拒绝,则返回的实例也是拒绝状态,一个待定,则返回的实例也是待定。
Promise.race()
也是接收一组期约实例,返回最先拒绝或解决的期约实例。
5、Promise思考题
1、打印什么
console.log(1);
let p = new Promise((resolve, reject) => {
console.log(2);
for (let i = 0; i < 10000; i++) {
if ((i === 5000)) {
resolve(3);
}
if ((i === 4000)) {
reject(4);
}
}
console.log(5);
});
p.then((res) => {
console.log(res);
}).catch((rej) => {
console.log(rej);
});
console.log(6);
2、打印什么
let a = new Promise((resolve) => {
console.log(1);
resolve();
}).then(() => {
console.log(2);
});
setTimeout(() => {
console.log(3);
});
let b = new Promise(async (resolve) => {
await a;
console.log(4);
await b;
console.log(5);
resolve();
}).then(() => {
console.log(6);
});