Promise
Promise的定义
Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。Promise对象属性如下图所示:
创建一个Promise实例的语法
var promise=new Promise(function(resolve,reject){
resolve()//或者reject()
});
Promise的作用
ECMAscript 6 原生提供了 Promise 对象。
Promise是JS 中进行异步编程的新解决方法
Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。
特点:
- 将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。流程更加清晰,代码更加优雅。
- Promise对象提供统一的接口,使得控制异步操作更加容易。
缺点:
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
- Promise一定要有返回值,不然无法更改状态
Promise的特性
Promise有三种状态:pending(等待)、fulfilled(已满足)和rejected(已拒绝)
Promise的状态只有两种走向:pending(等待)—>fulfilled(已满足)和pending(等待)—>rejected(已拒绝)
当状态为fulfilled(已满足)和或rejected(已拒绝),不会再变更状态
//当Promise对象创建到执行resolve()和reject()方法前,Promise对象一直处于pending(等待)状态
//如果执行的是resolve()方法,则pending(等待)—>fulfilled(已满足)
//如果执行的是reject()方法,则pending(等待)—>rejected(已拒绝)
var promise=new Promise(function(resolve,reject){
resolve()//或者reject()
});
.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
var p = new Promise(function (resolve, reject) {
resolve("成功执行resolve方法")
})
console.log(p);
console.dir(Promise);
function promise1() {
let p = new Promise(function (resolve, reject) {
setTimeout(function () {
var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
console.log('随机数生成的值:', num)
if (num <= 10) {
resolve(num);
}
else {
reject('数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
function promise2() {
let p = new Promise(function (resolve, reject) {
setTimeout(function () {
var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
console.log('随机数生成的值:', num)
if (num <= 10) {
resolve(num);
}
else {
reject('数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
function promise3() {
let p = new Promise(function (resolve, reject) {
setTimeout(function () {
var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
console.log('随机数生成的值:', num)
if (num <= 10) {
resolve(num);
}
else {
reject('数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
Promise
.all([promise3(), promise2(), promise1()])
.then(function (results) {
console.log(results);
});
最后输出结果为:最后一行输出的数组则是.all()方法输出的,只有当全部promise对象都成功变为fulfilled(已满足)状态,.all()方法才会成功返回
.allSettled()的作用与.all()方法类似,都是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
将上述例子所用的all方法改为allSettled方法,输出如下图:
从中我们不难看出**.all()方法与.allSettled()方法的区别**
- 1.返回的数据格式不同。all()返回一个直接包裹resolve内容的数组,则allSettled()返回一个包裹着对象的数组,每一个对象包含了当前Promise对象的状态以及返回值。
- 2.如果有一个Promise对象报错了,则all()无法执行,会报错你的错误,无法获得其他成功的数据。则allSettled()方法是不管有没有报错,把所有的Promise实例的数据都返回回来,放入到一个对象中。如果是resolve的数据则status值为fulfilled,相反则为rejected。
.race()的作用是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
将上述例子所用的all方法改为race方法,输出如下图:当第一个promise对象成功变为fulfilled(已满足)状态,.race()方法就会成功返回该对象的值,之后的对象如果也成功满足的话,会执行但结果会被抛弃。
Promise原型上的方法
.then():then会返回一个状态为pending(等待)的Promise
then方法接受的参数是函数,一个是成功的回调函数,一个是失败的函数,两个函数的参数为Promise对象的返回值或者resolve和reject方法中的参数。代码示例:
var p = new Promise(function (resolve, reject) {
var num=10
if(num<20){
resolve(num)
}else{
reject("你选的数太大了")
}
})
p.then((value)=>{
console.log("成功进入到then方法的成功回调",value);
},(reason)=>{
console.log("成功进入到then方法的失败回调",reason);
})
console.log(p);//成功进入到then方法的成功回调 10
.then()方法中的第一个参数为成功的回调函数,value参数为resolve()方法中的值;而第二个参数为失败的回调函数,reason参数为reject()方法中的值。
.then()方法是可以多次调用的,多次调用可以解决回调地狱的问题。何谓回调地狱?
简单来讲,回调函数嵌套回调函数即为回调地狱,示例代码:
setTimeout(function () { //第一层
console.log('回调第一层');
setTimeout(function () { //第二程
console.log('回调第二层');
setTimeout(function () { //第三层
console.log('回调第三层');
}, 1000)
}, 2000)
}, 3000)
总的来说:回调地狱就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。而通过Promise可以解决该问题,示例代码如下:
function fn(str){
var p=new Promise(function(resolve,reject){
//处理异步任务
var flag=true;
setTimeout(function(){
if(flag){
resolve(str)
}
else{
reject('操作失败')
}
})
})
return p;
}
fn('回调第一层')
.then((data)=>{
console.log(data);
return fn('回调第二层');
})
.then((data)=>{
console.log(data);
return fn('回调第三层')
})
.then((data)=>{
console.log(data);
})
.catch((data)=>{
console.log(data);
})
输出结果与上述结果一样,使用Promise可以解决回调地狱问题,增强代码可读性。
.catch()
Promise对象中的reject是用来抛出异常的,而.catch()就是用来捕获异常的,也就是和then方法中接受的第二参数rejected的回调是一样的。如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。示例代码:
var p = new Promise(function (resolve, reject) {
var num=30
if(num<20){
resolve(num)
}else{
reject("你选的数太大了")
}
})
p.then((value)=>{
console.log("成功进入到then方法的成功回调",value);
}).catch(function(reason){
console.log("catch方法",reason);
})
console.log(p);//输出catch方法 你选的数太大了
.catch()方法与.then()中第二个参数的区别
- 1.reject是用来抛出异常的,catch是处理异常的
- 2.reject是promise的方法,而then和catch是promise实例的方法
- 3.如果在then的第一个函数里抛出了异常,后面的catch能捕获到,而then的第二个函数捕获不到
- 4.then的第二个参数和catch捕获错误信息的时候会就近原则,如果是promise内部报错,reject抛出错误后,then的第二个参数和catch方法都存在的情况下,只有then的第二个参数能捕获到,如果then的第二个参数不存在,则catch方法会捕获到
- 5.catch可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数。
.finally()
.finally()方法不管Promise对象最后的状态如何都会执行
.finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是无法知道Promise最终的状态是resolved还是rejected的
它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。
finally本质上是then方法的特例
注意:
- 1 Promise可以链式调用,因为每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用。
- 2 .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环,会报如下错误:
Uncaught (in promise) TypeError: Chaining cycle detected for promise #\<Promise>
Promise的应用场景
Promise.all
1.多个请求结果合并在一起
- 具体描述:一个页面有多个请求,需要将所有请求的数据渲染到页面上
2.合并请求结果并处理错误
- 描述:我们需求单独处理一个请求的数据渲染和错误处理逻辑,有多个请求,我们就需要在多个地方写
3.验证多个请求结果是否都是满足条件
- 描述:在一个微信小程序项目中,做一个表单的输入内容安全验证,调用的是云函数写的方法,表单有多个字段需要验证,都是调用的一个内容安全校验接口,全部验证通过则 可以 进行正常的提交
Promise.race
1.图片请求超时
- 将请求图片成功函数和请求图片超时函数同时放在race方法中,当请求图片成功则会自动放弃请求图片超时函数的结果,如果请求图片失败,则会运行请求图片超时函数。
2.请求超时提示
- 原理同上
Promise.prototype.then
1.下个请求依赖上个请求的结果
- 类似微信小程序的登录,首先需要 执行微信小程序的 登录 wx.login 返回了code,然后调用后端写的登录接口,传入 code ,然后返回 token ,然后每次的请求都必须携带 token,即下一次的请求依赖上一次请求返回的数据。
2.中间件功能使用
- 接口返回的数据量比较大,在一个then 里面处理 显得臃肿,多个渲染数据分别给个then,让其各司其职
参考链接:Promise的常见应用场景有哪些
Promise面试题
1.请试写出以下代码的输出顺序。
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function() {
console.log("setTimeout");
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise1");
resolve();
}).then(function() {
console.log("promise2");
});
console.log('script end')
输出结果为:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
在理解该题目前,我需要引入以下概念:
宏队列和微队列
宏队列:用来保存待执行的宏任务,,比如setTimeout、setInterval、script标签、DOM回调、ajax回调等。
微队列:用来保存待执行的微任务,比如:promise、async-await、Object.observe等。
执行顺序:
1.执行一个宏任务(栈中没有就从事件队列中获取)
2.执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
3.宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
4.当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
5.渲染完毕后,JS线程会继续接管,开始下一个宏任务(从事件队列中获取)
该题的执行顺序是:
- 首先在script宏任务中,会先输出script start,然后遇到setTimeout压入宏任务队列中
- 执行async1微任务,输出async1 start;再调用async2,输出async2;此时await阻塞下面的代码只想你给,先执行下一个微任务,输出promise1
- 然后在script宏任务中执行script end;执行第二轮宏任务,在执行宏任务之前将微任务执行完成,依次输出async1 end和promise2,最后输出setTimeout