【ES6知识点】——Promise对象

文章目录

  • Promise
    • Promise的定义
    • Promise的作用
    • Promise的特性
      • Promise原型上的方法
    • Promise的应用场景
    • Promise面试题

Promise

Promise的定义

   Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。Promise对象属性如下图所示:
【ES6知识点】——Promise对象_第1张图片
创建一个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()方法才会成功返回
【ES6知识点】——Promise对象_第2张图片

.allSettled()的作用与.all()方法类似,都是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。

将上述例子所用的all方法改为allSettled方法,输出如下图:
【ES6知识点】——Promise对象_第3张图片
从中我们不难看出**.all()方法与.allSettled()方法的区别**

  • 1.返回的数据格式不同。all()返回一个直接包裹resolve内容的数组,则allSettled()返回一个包裹着对象的数组,每一个对象包含了当前Promise对象的状态以及返回值。
  • 2.如果有一个Promise对象报错了,则all()无法执行,会报错你的错误,无法获得其他成功的数据。则allSettled()方法是不管有没有报错,把所有的Promise实例的数据都返回回来,放入到一个对象中。如果是resolve的数据则status值为fulfilled,相反则为rejected。

.race()的作用是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
将上述例子所用的all方法改为race方法,输出如下图:当第一个promise对象成功变为fulfilled(已满足)状态,.race()方法就会成功返回该对象的值,之后的对象如果也成功满足的话,会执行但结果会被抛弃。
【ES6知识点】——Promise对象_第4张图片

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

你可能感兴趣的:(前端——ES6,前端,es6,javascript,前端)