什么是Promise?(异步编程的解决方案)
在JavaScript中,Promise是一种异步编程的解决方案【从语法上讲:Promise是一个对象,从它可以获取异步操作的消息】,用于处理异步操作的结果,Promise可以使异步操作更加容易管理和更可读,不需要嵌套回调函数,避免回调地狱场景出现
异步函数: js执行任务时,一次只能执行一个任务,会阻塞其他任务,而异步任务是当遇到该方法时,它不会阻塞程序的执行流程,程序会继续往下执行;当方法执行完毕后,程序能够获取执行完毕的消息或结果
回调函数: 当一个函数作为参数传入函数中,且传入的参数函数满足一定条件后执行,则这种参数函数就叫做回调函数
回调地狱: 回调函数继续进行回调
//回调地狱
setTimeout(function() {
console.log('第一层回调');
setTimeout(function() {
console.log('第二层回调');
setTimeout(function() {
console.log('第三层回调');
... //n层回调, 形成了回调地狱
}, 1000)
}, 2000)
}, 3000)
Promise的三种状态
- pending【进行中】
- fulfilled【已成功】
- rejected【已失败】
注意: Promise的状态只能从pending转换成fulfilled或rejected状态,一旦状态变更后,就不会再改变
Promise的用法
Promise的构造函数:
Promise的构造函数接受一个参数,即回调函数,且这个回调函数需传入两个参数:resolve、reject【传入的参数本身就是函数】
- resolve:异步操作执行成功后的回调函数【把Promise的状态从进行中变为成功状态,pending-->fulfilled】
- reject:异步操作执行失败后的回调函数【把Promise的状态从进行中变为拒绝状态,pending-->reject】
let my_promise = new Promise((resolve, reject) => {
//异步操作,eg:接口请求...
setTimeout(() => {
console.log('promise里的异步函数');
resolve(); //执行成功
)
})
Promise回调:
1. Promise.then:
(1) 执行resolve时,Promise状态从pending变为fulfilled,会执行Promise.then方法;
(2) Promise.then方法可以接受一个回调函数作为参数,该回调函数携带一个参数,该参数就是resolve(res)返回的数据
(3) Promise.then方法也可以接受两个回调函数作为参数,第一个回调函数携带一个参数,该参数是resolve(res)返回的数据;第二个回调函数携带也携带一个参数,该参数是reject(err)返回的参数
(4) 若Promise对象的状态是rejected,且此时then方法没有设置第二个参数,就会向外抛出一个错误,使catch捕捉到
(5) then方法的返回值是一个新的promise对象,与调用then方法的不是同一个promise对象
//then只有一个回调函数作为参数
let my_promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行到resolve啦'); //执行成功
)
}).then(res => {
console.log(res); //执行到resolve啦
})
//then有两个回调函数作为参数
let my_promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行到resolve啦'); //执行成功
)
}).then(res => {
console.log(res); //执行到resolve啦
}, err => {
console.log(err); //没有执行reject,所以该回调函数不执行
})
2. Promise.catch:
(1) 执行reject时,Promise状态从pending变为rejected,会执行Promise.catch方法;
(2) Promise.catch方法接受一个回调函数作为参数,该回调函数携带一个参数,该参数就是reject(err)返回的数据
(3) Promise.catch的作用与Promise.then方法的第二个参数效果相同,但它还可以捕捉到then中抛出的异常,不会阻塞其他任务
let my_promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('哎呀,要执行失败了'); //失败状态
)
}).then(res => {
console.log(res); //此时then中的方法不执行,因为没有执行resolve
}).catch(err => {
console.log(err); //哎呀,要执行失败了
})
3. Promise.finally:
(1) 无论Promise的状态是resolved还是rejected都会执行其中的回调函数,只有在状态是pending时,finally的函数是不会执行的;其返回值是一个Promise,且保留原promise对象的状态和值
(2) Promise.finally是不会改变promise的状态
Promise方法:
1. Promise.all:
(1) 传入一个数组参数,数组中均是Promise,若数组中的元素不是Promise对象,则会使用Promise.resolve将其转成Promise对象
(2) Promise.all可以将多个Promise实例包装成一个新的Promise实例;成功和失败的返回值是不同的,成功时返回的是一个结果数组,失败时返回的是最先被reject失败状态的值
(3) Promise.all提供了并行执行异步操作的能力,并且在所有异步操作完成之后,统一返回所有结果;数组中所有的Promise都执行成功则成功,可以执行then方法;若数组中有一个Promise执行失败,则返回失败,执行catch方法
// 实例一
const a = new Promise(resolve => resolve('a'));
const b = new Promise(resolve => resolve('b'));
Promise.all([a, b]).then(res => {
console.log('all', res); // ['a', 'b']
// res返回一个数组,其元素对应传入参数Promise的返回值
})
// 实例二(成功失败案例对比)
const p1 = new Promise((resolve, reject) => {
resolve('成功了');
})
const p2 = new Promise((resolve, reject) => {
resolve('success');
})
const p3 = Promise.reject('error');
const p4 = Promise.reject('失败');
//成功时
Promise.all([p1, p2]).then(res => {
console.log(res); //p1,p2都是成功态 ['成功', 'success']
}).catch(err => {
console.log(err); //此时不执行
})
//失败时
Promise.all([p1, p3, p2, p4]).then(res => {
console.log(res); //此时有一个失败状态,所以不走then
}).catch(err => {
console.log(err); //p3是失败状态,且是Promise数组中返回的第一个失败状态,error
})
2. Promise.race:
(1) 用法以及传入的参数与Promise.all一样,只是返回的结果不同
(2) Promise.race返回的是执行最快的那个Promise的结果,不管结果本身是成功状态还是失败状态
const a = new Promise(resolve => {
setTimeout(() => {
resolve('a');
}, 1000)
})
const b = new Promise(resolve => {
setTimeout(() => {
resolve('b');
}, 2000)
})
Promise.race([a, b]).then(res => {
console.log('race', res); //返回a,a比较先返回结果
})
Promise特性
- 立即执行性:new Promise相当于new Function,这段代码是同步执行的,不会异步执行;而then回调方法是异步的
// Promise的立即执行性
const p = new Promise((resolve, reject) => {
console.log('create a promise');
resolve('success');
})
console.log('after p');
p.then(value => {
console.log(value);
})
// 输出create a promise --> after p --> success
// 如果没有p.then(),也会输出create a promise --> after p
- Promise状态的不可逆性:
const p1 = new Promise((resolve, reject) => {
resolve('p1-success1');
resolve('p1-success2');
})
p1.then(res => {
console.log(res); //p1-success1
}).then(res => {
console.log(res); //undefined,上一个then没有返回值
}).catch(err => {
console.log(err); //不执行
});
const p2 = new Promise((resolve, reject) => {
resolve('p2-success1');
reject('p2-error1');
});
p2.then(res => {
console.log(res); //p2-success1
}).catch(err => {
console.log(err); //不执行,此时的状态已经是resolved,所以不执行reject函数
});
const p3 = new Promise((resolve, reject) => {
reject('p3-error1');
resolve('p3-success1');
});
p3.then(res => {
console.log(res); //不执行,此时的状态已经是rejected,所以不执行resolve函数
}).catch(err => {
console.log(err); //p3-error1
})
Promise链式调用顺序🌰
示例一:
-
promise的then/catch方法执行后也会返回一个promise
-
当执行then方法时,若前面的promise已经是resolved状态,则直接将回调放入微任务队列中;若前面的promise是pending,则会将回调存储在promise内部,一直等到promise被resolve才会将回调函数推入到微任务队列中
-
执行then方法是同步的,而then中的回调是异步的
-
then方法值负责注册回调,由resolve将注册的回调放入微任务队列中,由事件循环将其取出并执行
-
若then方法返回的promise是没有resolve函数的,取而代之只要then中回调的代码执行完毕并获得同步返回值,这个then返回的promise就算是被resolve
-
链式调用的第二个then是第一个then返回的promise resolve()的回调函数
new Promise((resolve, reject) => {
console.log("promise1"); // 同步任务,立即执行
resolve(); //此时是resolved状态,将then中的回调函数放入微任务队列中
}).then(() => {
console.log("then11"); // 微任务队列中的第一个任务,
new Promise((resolve, reject) => {
console.log("promise2");
resolve();
}).then(() => {
console.log("then21");
}).then(() => {
console.log("then23")
})
// 第一个promise的then中没有返回值,直接默认返回resolve()
}).then(() => {
console.log("then12")
})
// 打印顺序:promise1 then11 promise2 then21 then12 then23
执行思路:
- 先执行同步任务,所以首先是 promise1
- 遇到resolve(),此时promise的状态为"resolved",将第一个then方法放入微任务队列中
- 无其他同步任务,执行微任务,此时打印"then11",new Promise()也是同步任务,所以紧跟着打印"promise2"
- 遇到promise2的resolve(),将promise2的第一个then中的回调函数放入微任务中,第二个then中的回调函数由于第一个then返回的promise还在pending中,所以存储在promise内部
- 此时promise1的同步函数执行完且没有返回值,直接resolve,所以将promise1的第二个then放入微任务队列中,promise2的第一个then的回调函数执行,并无返回值,则将promise2的第二个then的回调函数放入微任务中,所以先打印then21,接着打印then12,最后打印then23
示例二:
new Promise((resolve, reject) => {
console.log("promise1");
resolve();
}).then() => {
console.log("then11");
return new Promise((resolve, reject) => {
console.log("promise2");
resolve();
}).then(() => {
console.log("then21");
}).then(() => {
console.log("then23");
})
}).then(() => {
console.log("then12");
})
// 打印顺序:promise1 then11 promise2 then21 then23 then12
执行思路:
- 先执行同步任务,首先打印promise1
- 遇到promise1的resolve,将第一个then的回调函数放入微任务队列中
- 同步任务执行完,开始执行微任务,打印then11,new Promise也是同步任务,接着打印promise2
- promise1中的第一个then有定义新的Promise作为返回值,所以此时的promise1的第二个then中,promise状态为pending,因此还存储在promise内部
- promise2的new Promise中遇到了resolve(),所以将第一个then的回调函数放入微任务中,且第一个then没有返回值,所以默认执行resolve(),将第二个then的回调函数放入微任务中,并执行resolve()
- 此时promise2的一系列执行完毕,返回状态为resolve的Promise,所以此时将promise1的第二个then中的回调函数放入微任务队列中
示例三:
- 链式调用的注册是前后依赖的,比如链式调用的第二个then的注册,是需要链式调用的第一个then执行完成
- 变量定义的方式,注册都是同步的,即then与new Promise都是同步执行的
new Promise((resolve, reject) => {
console.log("promise1");
resolve();
}).then(() => {
console.log("promise1的then1");
let p = new Promise((resolve, reject) => {
console.log("promise2");
resolve();
});
p.then(() => {
console.log("promise2的then1");
});
p.then(() => {
console.log("promise2的then2");
});
}).then(() => {
console.log("promise1的then2");
});
// 顺序打印:promise1 promise1的then1 promise2 promise2的then1 promise2的then2 promise1的then2
执行思路:
- 先执行同步任务,打印"promise1" 2.遇到resolve,将promise1的第一个then中的回调函数放入微队列中,promise1的第二个then,由于此时的第一个then的promise状态还是pending,所以其存储在promise内部 3.执行promise1的第一个then的回调函数中的同步任务,打印promise1的then1和promise2 4."promise2的then1"和"promise2的then2"由于此时的promise2的状态已经是resolve,所以按顺序放入微任务队列中 5.此时同步任务执行完,promise1的then1没有返回值,所以默认执行resolve(),即将"promise1的then2"放入微任务队列 6.顺序执行微任务
示例四:
new Promise((resolve, reject) => {
console.log("promise1");
resolve();
}).then(() => {
console.log("promise1的then1");
new Promise((resolve, reject) => {
console.log("promise2");
resolve();
}).then(() => {
console.log("promise2的then1");
}).then(() => {
console.log("promise2的then2");
});
return new Promise((resolve, reject) => {
console.log("promise3");
resolve();
}).then(() => {
console.log("promise3的then1");
}).then(() => {
console.log("promise3的then2");
})
}).then(() => {
console.log("promise1的then2");
})
// 顺序打印:promise1 promise1的then1 promise2 promise3 promise2的then1 promise3的then1
// promise2的then2 promise3的then2 promise1的then2
执行思路:
- 执行同步任务,打印promise1
- 遇到resolve(),将promise1的then中的回调函数放入微任务队列中,执行完同步任务,开始执行微任务队列中的then
- 打印promise1的then1的同步任务,new Promise也是同步任务,打印promise2,遇到promise2的resolve(),将promise2的第一个then的回调函数放入微任务队列中,第二个then由于第一个then的Promise状态还是pending,所以存储在promise内部
- 遇到promise1的return,且return的是一个Promise对象,所以执行promise3的同步任务,打印promise3,遇到promise3的resolve(),所以将promise3的第一个then放入微任务队列中
- 此时已经没有同步任务,所以执行微任务的第一个任务,即打印"promise2的then1",且其无返回值,默认执行resolve(),所以将promise2的then2放入到微任务队列中
- 执行微任务队列的第二个任务,即打印"promise3的then1",且执行resolve,将promise3的then2放入微任务队列中
- 继续执行微任务下一个任务,打印"promise2的then2"
- 继续执行微任务下一个任务,打印"promise3的then2"
- 此时promise1的then1的返回的Promise是resolve状态,所以将promise1的then2的回调函数放入微任务队列中,打印"promise1的then2"