8.3 Promise的基本使用
const pro = new Promise((resolve, reject)=>{
// 未决阶段的处理
// 通过调用resolve函数将Promise推向已决阶段的resolved状态
// 通过调用reject函数将Promise推向已决阶段的rejected状态
// resolve和reject均可以传递最多一个参数,表示推向状态的数据
})
pro.then(data=>{
//这是thenable函数,如果当前的Promise已经是resolved状态,该函数会立即执行
//如果当前是未决阶段,则会加入到作业队列,等待到达resolved状态后执行
//data为状态数据
}, err=>{
//这是catchable函数,如果当前的Promise已经是rejected状态,该函数会立即执行
//如果当前是未决阶段,则会加入到作业队列,等待到达rejected状态后执行
//err为状态数据
})
细节:
未决阶段的处理函数是同步的,会立即执行
thenable和catchable函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列
- pro.then可以只添加thenable函数,pro.catch可以单独添加catchable函数
- 在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向rejected,并会被catchable捕获
一旦状态推向了已决阶段,无法再对状态做任何更改
Promise并没有消除回调,只是让回调变得可控
function biaobai(god) {
return new Promise(resolve => {
console.log(`邓哥向${god}发出了表白短信`);
setTimeout(() => {
if (Math.random() < 0.3) {
//女神同意拉
resolve(true)
} else {
//resolve
resolve(false);
}
}, 3000);
})
}
biaobai("女神1").then(result => {
console.log(result);
})
const pro = new Promise((resolve, reject) => {
console.log("未决阶段")
setTimeout(() => {
if (Math.random() < 0.5) {
resolve(123)
} else {
reject(new Error("asdfasdf"));
}
}, 3000);
})
pro.then(data => {
console.log(data);
}, err => {
console.log(err)
})
const pro = new Promise((resolve, reject) => {
console.log("a")
resolve(1);
setTimeout(() => {
console.log("b") // 宏队列
}, 0);
})
//pro: resolved
pro.then(data => {
console.log(data) // 微队列
})
pro.catch(err => {
console.log(err)
})
console.log("c")
// 执行顺序是:a c 1 b
const pro = new Promise((resolve, reject) => {
throw new Error("123"); // pro: rejected
})
pro.then(data => {
console.log(data) // 不会执行
})
pro.catch(err => {
console.log(err) // Error: 123
})
// 一旦状态推向了已决阶段,无法再对状态做任何更改
const pro = new Promise((resolve, reject) => {
throw new Error("abc");
resolve(1); //无效
reject(2); //无效
resolve(3); //无效
reject(4); //无效
})
pro.then(data => {
console.log(data)
})
pro.catch(err => {
console.log(err) // Error: abc
})
8.4 Promise的串联
当后续的Promise需要用到之前的Promise的处理结果时,这时就需要Promise的串联。
Promise对象中,无论是then方法还是catch方法,它们都具有返回值,返回的是一个全新的Promise对象,它的状态满足下面的规则:
如果当前的Promise是未决的,得到的新的Promise是挂起状态
如果当前的Promise是已决的,会运行相应的后续处理函数,并将后续处理函数的结果(返回值)作为resolved状态数据,应用到新的Promise中;如果后续处理函数发生错误,则把返回值作为rejected状态数据,应用到新的Promise中。
后续的Promise一定会等到前面的Promise有了后续处理结果后,才会变成已决状态
then返回的Promise的对象一开始一定是挂起状态。
注意:
thenable和catchable函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列。
const pro1 = new Promise((resolve, reject) => {
resolve(1)
})
const pro2 = pro1.then(result => {
return result * 2
});
//pro2类型:Promise对象
//pro2的状态:
pro2.then(result => console.log(result * 2), err => console.log(err * 3))
//输出:4
const pro1 = new Promise((resolve, reject) => {
throw 1;
})
const pro2 = pro1.then(result => {
return result * 2
}, err => {
return err * 3;
});
pro1.catch(err => {
return err * 2;
})
//pro2类型:Promise对象
//pro2的状态:
pro2.then(result => console.log(result * 2), err => console.log(err * 3))
//输出:6
const pro1 = new Promise((resolve, reject) => {
throw 1;
})
const pro2 = pro1.then(result => {
return result * 2
}, err => {
throw err;
});
//pro2类型:Promise对象
//pro2的状态:
pro2.then(result => console.log(result * 2), err => console.log(err * 3))
//输出:3
如果前面的Promise的后续处理,返回的是一个Promise,则返回的新的Promise状态和后续处理返回的Promise状态保持一致。
const pro1 = new Promise((resolve, reject) => {
resolve(1);
})
const pro2 = new Promise((resolve, reject) => {
resolve(2);
})
const pro3 = pro1.then(result =>{
return pro2;
})
pro3.then(result => {
console.log(result);
})
// 输出:2
const pro1 = new Promise((resolve, reject) => {
resolve(1);
})
const pro2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 3000);
})
pro1.then(result => {
console.log("结果出来了,得到的是一个Promise")
return pro2;
}).then(result => {
console.log(result) // 2
}).then(result => {
console.log(result) // undefined 上一次没有返回值,所以此处返回undefined
})
// 输出: 结果出来了,得到的是一个Promise
// 2 (等两秒)
// undefined
//获取李华所在班级的老师的信息 (数据在本地)
//1. 获取李华的班级id Promise
//2. 根据班级id获取李华所在班级的老师id Promise
//3. 根据老师的id查询老师信息 Promise
const pro = ajax({
url: "./data/students.json"
})
pro.then(resp => {
for (let i = 0; i < resp.length; i++) {
if (resp[i].name === "李华") {
return resp[i].classId; //班级id
}
}
}).then(cid => {
return ajax({
url: "./data/classes.json?cid=" + cid
}).then(cls => {
for (let i = 0; i < cls.length; i++) {
if (cls[i].id === cid) {
return cls[i].teacherId;
}
}
})
}).then(tid => {
return ajax({
url: "./data/teachers.json"
}).then(ts => {
for (let i = 0; i < ts.length; i++) {
if (ts[i].id === tid) {
return ts[i];
}
}
})
}).then(teacher => {
console.log(teacher);
})
function biaobai(god) {
return new Promise(resolve => {
console.log(`邓哥向${god}发出了表白短信`);
setTimeout(() => {
if (Math.random() < 0.3) {
//女神同意拉
resolve(true)
} else {
//resolve
resolve(false);
}
}, 500);
})
}
/*
邓哥心中有五个女神
有一天,邓哥决定向第一个女神表白,如果女神拒绝,则向第二个女神表白,直到所有的女神都拒绝,或有一个女神同意为止
用代码模拟上面的场景
*/
const gods = ["女神1", "女神2", "女神3", "女神4", "女神5"];
let pro;
for (let i = 0; i < gods.length; i++) {
if (i === 0) {
pro = biaobai(gods[i]);
}
pro = pro.then(resp => {
if (resp === undefined) {
return;
} else if (resp) {
console.log(`${gods[i]}同意了`)
return;
} else {
console.log(`${gods[i]}拒绝了`)
if (i < gods.length - 1) {
return biaobai(gods[i + 1]);
}
}
})
}
8.5 Promise的其他api
原型成员 (实例成员)
then
:注册一个后续处理函数,当Promise为resolved状态时运行该函数catch
:注册一个后续处理函数,当Promise为rejected状态时运行该函数finally
:[ES2018]注册一个后续处理函数(无参),当Promise为已决时运行该函数
// 无论是resolve还是reject的, finally 都会执行,并且是无需传参的。
const pro = new Promise((resolve, reject) => {
resolve(1);
})
pro.finally(() => console.log("finally1"))
pro.then(resp => console.log("then2", resp * 2));
pro.catch(resp => console.log("catch2", resp * 2));
// finally1 then2 2
构造函数成员 (静态成员)
-
resolve(数据):该方法返回一个resolved状态的Promise,传递的数据作为状态数据
- 特殊情况:如果传递的数据是Promise,则直接返回传递的Promise对象 -
reject(数据):该方法返回一个rejected状态的Promise,传递的数据作为状态数据
const pro = new Promise((resolve, reject) => {
resolve(1);
})
//等效于:
const pro = Promise.resolve(1);
const pro = new Promise((resolve, reject) => {
reject(1);
})
//等效于:
const pro = Promise.reject(1);
resolve的特殊情况:如果传递的数据是Promise,则直接返回传递的Promise对象
const p = new Promise((resolve, reject) => {
resolve(3);
})
// const pro = Promise.resolve(p);
//等效于
const pro = p;
console.log(pro === p) // true
all(iterable)
:这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
const proms = [];
for (let i = 0; i < 10; i++) {
proms.push(new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.5) {
console.log(i, "完成");
resolve(i);
} else {
console.log(i, "失败")
reject(i);
}
}, getRandom(1000, 5000));
}))
}
//等到所有的promise变成resolved状态后输出: 全部完成
const pro = Promise.all(proms)
pro.then(datas => {
console.log("全部完成", datas); // 只有当全部完成才会执行
})
pro.catch(err => {
console.log("有失败的", err); // 有失败的会立即执行这里,
})
console.log(proms);
race(iterable)
:当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
const proms = [];
for (let i = 0; i < 10; i++) {
proms.push(new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.5) {
console.log(i, "完成");
resolve(i);
} else {
console.log(i, "失败")
reject(i);
}
}, getRandom(1000, 5000));
}))
}
//等到所有的promise变成resolved状态后输出: 全部完成
const pro = Promise.race(proms)
pro.then(data => {
console.log("有人完成了", data); // 只要有人完成就执行。
})
pro.catch(err => {
console.log("有人失败了", err); // 只要有人失败就执行。
})
console.log(proms);
/*
邓哥心中有二十个女神,他决定用更加高效的办法
他同时给二十个女神表白,如果有女神同意,就拒绝其他的女神
并且,当所有的女神回复完成后,他要把所有的回复都记录到日志进行分析
用代码模拟上面的场景
*/
function biaobai(god) {
return new Promise((resolve, reject) => {
console.log(`邓哥向女神【${god}】发出了表白短信`);
setTimeout(() => {
if (Math.random() < 0.05) {
//女神同意拉
console.log(god, "同意")
resolve(true);
} else {
console.log(god, "拒绝")
resolve(false);
}
}, Math.floor(Math.random() * (3000 - 1000) + 1000));
})
}
const proms = [];
let hasAgree = false; //是否有女神同意
for (let i = 1; i <= 20; i++) {
const pro = biaobai(`女神${i}`).then(resp => {
if (resp) {
if (hasAgree) {
console.log("发错了短信,邓哥很机智的拒绝了")
} else {
hasAgree = true;
console.log("邓哥很开心,终于成功了!");
}
}
return resp;
})
proms.push(pro);
}
Promise.all(proms).then(results => {
console.log("日志记录", results);
})
async和await
async
和 await
是 ES2016 新增两个关键字,它们借鉴了 ES2015 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。
async
目的是简化在函数的返回值中对Promise的创建
async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。
async function test(){
console.log(1);
return 2;
}
//等效于
function test(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
await
await关键字必须出现在async函数中!!!!
await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable中的状态数据。
async function test1(){
console.log(1);
return 2;
}
async function test2(){
const result = await test1();
console.log(result);
}
test2();
//等效于
function test1(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
function test2(){
return new Promise((resolve, reject)=>{
test1().then(data => {
const result = data;
console.log(result);
resolve();
})
})
}
test2();
举栗子:
async function test1() {
console.log(1);
return 2;
}
async function test2() {
const result = await test1();
console.log(result);
}
test2();
// 输出 1 2
//还是获取李华所在班级的老师的信息
//1. 获取李华的班级id Promise
//2. 根据班级id获取李华所在班级的老师id Promise
//3. 根据老师的id查询老师信息 Promise
async function getTeacher() {
const stus = await ajax({
url: "./data/students.json"
})
let cid;
for (let i = 0; i < stus.length; i++) {
if (stus[i].name === "李华") {
cid = stus[i].classId;
}
}
const cls = await ajax({
url: "./data/classes.json"
})
let tid;
for (let i = 0; i < cls.length; i++) {
if (cls[i].id === cid) {
tid = cls[i].teacherId;
}
}
const ts = await ajax({
url: "./data/teachers.json"
})
for (let i = 0; i < ts.length; i++) {
const element = ts[i];
if (element.id === tid) {
console.log(element);
}
}
}
getTeacher();
// 这样子书写就看着很舒服了!!!!
function biaobai(god) {
return new Promise(resolve => {
console.log(`邓哥向${god}发出了表白短信`);
setTimeout(() => {
if (Math.random() < 0.3) {
//女神同意拉
resolve(true)
} else {
//resolve
resolve(false);
}
}, 500);
})
}
/*
邓哥心中有三个女神
有一天,邓哥决定向第一个女神表白,如果女神拒绝,则向第二个女神表白,直到所有的女神都拒绝,或有一个女神同意为止
用代码模拟上面的场景
*/
(async () => {
const gods = ["女神1", "女神2", "女神3", "女神4", "女神5"];
for (let i = 0; i < gods.length; i++) {
const g = gods[i];
// 当前循环等待的Promise没有resolve,下一次循环不运行
const result = await biaobai(g);
if (result) {
console.log(`${g}同意了,不用再表白了!!!`);
break;
} else {
console.log(`${g}没有同意`)
}
}
})()
// 通过改造计时器函数,从而方便使用async
function delay(duration) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, duration);
})
}
async function biaobai(god) {
console.log(`邓哥向${god}发出了表白短信`);
await delay(500);
return Math.random() < 0.3;
}
(async () => {
const gods = ["女神1", "女神2", "女神3", "女神4", "女神5"];
for (let i = 0; i < gods.length; i++) {
const g = gods[i];
// 当前循环等待的Promise没有resolve,下一次循环不运行
const result = await biaobai(g);
if (result) {
console.log(`${g}同意了,不用再表白了!!!`);
break;
} else {
console.log(`${g}没有同意`)
}
}
})()
如果await的表达式不是Promise,则会将其使用Promise.resolve包装后按照规则运行。
async function test() {
const result = await 1;
console.log(result)
}
// 相当于这种写法:
//function test(){
// return new Promise((resolve,reject) =>{
// Promise.resolve(1).then(data =>{
// const result = data;
// console.log(result);
// resolve();
// })
// })
//}
test();
console.log(123); // 先输出123 在输出1
若需要拿到错误状态,可以利用try catch进行处理。
async function getPromise() {
if (Math.random() < 0.5) {
return 1;
} else {
throw 2;
}
}
async function test() {
try {
const result = await getPromise();
console.log("正常状态", result)
} catch (err) {
console.log("错误状态", err);
}
}
test();