• Promise JS Promise对象 学会使用Promise 理解Promise


    一、什么是Promise?

      概念:

      Promise,直译为“承诺”,是异步编程的一种解决方案,ECMAscript 6 原生提供了 Promise 对象。

      Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。

      用途:

      一般来讲,Promise可以用来避免异步操作函数里的嵌套回调(callback hell)问题,但其实Promise本身就是一种比回调异步更强大的异步解决方案。

      JS是单线程非阻塞语言,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。

      而解决异步最直接的方法是回调嵌套,但如果回调次数太多,就很容易进入“回调地狱”。

    setTimeout(function () {
                console.log("第1秒,做第一件事");
                setTimeout(function () {
                    console.log("第2秒,做第二件事");
                    setTimeout(function () {
                        console.log("第3秒,做第三件事");
                        setTimeout(function () {
                            console.log("第4秒,做第四件事");
                            setTimeout(function () {
                                console.log("第5秒,做第五件事");
                                setTimeout(function () {
                                    console.log("第6秒,做第六件事");
                                }, 1000);
                            }, 1000);
                        }, 1000);
                    }, 1000);
                }, 1000);
            }, 1000);

      如上,这样的层层嵌套,越多越恐怖,代码不清晰,易读性直线下降,维护难度直线上升。而Promise就可以解决这个问题。

    注:Promise最早由社区提出并有多种开源实现,ES6将其写进了语言标准,统一了用法,并原生提供了Promise对象。

      Promise最大的好处其实是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了,见下文。

      实现异步的方法目前有,自定义嵌套、Promise、generator、Defferd,还有ES7的async(其实是generator的封装),

    不同场景可以选择不同的方式去实现。

    二、Promise 对象

      1、Promise 创建

      Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由JavaScript引擎提供,不用自己部署。

    要想创建一个 promise 对象,直接使用 new 来调用 Promise 的构造器进行实例化。

    var promise = new Promise(function (resolve, reject) {
                // 异步处理
                if (true/* 异步操作成功 */) {
                    resolve(value);
                } else {
                    reject(error);
                }
            });

    注:很多时候不写reject,因为只期望它成功,但如果不写,就没法报错(处理失败的情况)。

      2、Promise 状态和PromiseValue

      每个Promise对象都有三种状态:pending(已就绪)、fulfilled(已成功)和 rejected(已失败)。

    当对象创建成功,即为pending状态,如果成功,通过调用resolve方法变为fulfilled状态,如果失败,通过调用reject方法变为rejected状态。

    改变状态时,可以选择传递消息(value或error),就等于PromiseValue的值,也可以不传,PromiseValue会等于undefined,其实“状态改变”本身就是一个消息,。

    一旦创建即执行,相当于立即执行,然后返回了一个pending状态的Promise对象

    调用方法改变状态:

    resolve: 把Promise状态 从 pending 变为 resolved ,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。 

    reject: 把Promise状态 从 pending 变为 reject ,在异步操作失败时调用,并把错误作为参数传递出去

     注:上述例子每次给pro变量赋值了一个新的Promise对象,因为只有Promise对象本身的处理结果能够改变它的状态,任何其他操作都无法改变Promise对象的状态。

      状态的特点:

      状态只能通过resolve和reject方法改变,并且一旦调用一次改变后,状态就定型(PromiseValue也会固定)不会再变。所以Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变两种情况。

      3、then 方法

      Promise实例生成后,可用then方法分别指定两种状态的回调函数,即为Promise实例添加状态改变时的回调函数。并且可以接住resolve或reject传递的参数(结果)。

    具体实现:

      then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,必选,第二个参数是 Promise 执行失败时的回调,可选;两个函数只会有一个被调用。

        

    promise.then(function (value) {
                //成功时调用 value即为resolve传递的结果 不传为undefined
            }, function (error) {
                //失败时调用 error即为reject传递的结果  不传为undefined
            });

    then 方法的特点:在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。

    (function runme() {
                var i = 0;
                new Promise(function (resolve) {
                    resolve();
                }).then(function () {
                    i++
                });
                alert(i);//0  此时回调函数还没有执行
            })()

      then方法会返回一个Promise对象,这个对象的PromiseValue通过then调用的函数里的return赋值。

    注:如果return的是一个Promise对象,then就会直接返回这个对象。

      4、链式写法

      then方法返回的是一个新的Promise实例,(注意:不是原来那个Promise实例)。因此可以采用链式写法。

    A().then(function (people) {
                return Promise.all([B(people), C(people)]);
            }).then(function (people) {
                D(people);
            }).catch(function(error){
                throw error;
            });

    注:A().then(function (people) {}   返回的对象就是  Promise.all([B(people), C(people)])得到的对象。

      Promise.all(),见下文。

      5、catch方法

      catch 相当于 .then(null, rejection)的别名,用于指定发生错误时的回调函数。then函数中的第二个参数常常被省略了,然后被这个catch方法替代。

      一旦catch前面的任何一个Promise发生异常(调用reject方法),都会被catch捕获,包括Promise函数创建的Promise,还有.then返回的Promise,甚至catch前面如果还有一个catch在这个catch抛出的异常(使用throw语句)也会被后一个catch捕获。

     注:此次catch方法返回的是一个resolved状态的Promise对象。

     注:此次catch方法返回的是一个rejected状态的Promise对象。

    继续.catch

     注:可以看到前面catch抛出的异常成功的被下一个catch捕获。

      也就是说:
      Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止,即,错误总会被下一个catch语句捕获。

      所以通常会这么写:

    promise.then().catch()
    promise.then().then().catch()
    promise.then().then().catch().then().catch()
    注:catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法和catch方法。
      一般总是建议Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。


      6、Promise.all() 和Promise.race()
     
      Promise.all() :将多个Promise实例,包装成一个新的Promise实例。内部所有的Promise的状态都变成fulfilled,这个Promise状态才会变成fulfilled,返回值是一个数组,但是只要有一个 rejected 这个Promise对象就会变成rejected 返回第一个被reject的实例的返回值。
     
      Promise.all() 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 对象的实例。(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例。)
      
      Promise.race():跟all方法一样,只是race就像是赛跑,谁先有结果,返回谁的结果。不会等到所有的Promise都执行完。
     
         let p1 = new Promise((resolve) => {
                setTimeout(function () {
                    console.log(1);
                    resolve(1);
                }, 1000)
            });
            let p2 = new Promise((resolve) => {
                setTimeout(function () {
                    console.log(2);
                    resolve(2);
                }, 3000)
            });
    
            let p3 = new Promise((resolve) => {
                setTimeout(function () {
                    console.log(3);
                    resolve(3);
                }, 4000)
            });
    
            let pp = Promise.all([p1, p2, p3]);
          let ppp = Promise.race([p1, p2, p3]);

      7、现有对象转为Promise对象

      Promise.resolve()方法就起到这个作用

    Promise.resolve('foo');
    // 等价于
    new Promise(function (resolve) {
        resolve('foo')
    });

      Promise.resolve()方法的参数分成四种情况:

      参数是一个Promise实例 :不做任何修改、原封不动地返回这个实例。

      参数是一个thenable对象 :将这个对象转换为Promise对象,然后立刻执行thenable的then方法

      参数是普通值(除了Promise对象和thenable对象的所有值),基础复杂类型函数都可以:反回一个新的Promise对象,状态为resolved,PromiseValue等于传入的参数。

      不带有任何参数::直接返回一个resolved状态的Promise对象


    注:立即resolved的对象,是在本轮事件循环结束时执行,而不是在下一轮循环时间开始时执行

    setTimeout(function () {
      console.log('three');
    }, 0);
    
    Promise.resolve().then(function () {
      console.log('two');
    });
    
    console.log('one');

    上面代码中,setTimeout(fn,0),在下一轮循环事件开始执行,Promise.resolve()在本轮事件循环结束时执行,console.log('one')立刻执行,

    因此上面的打印顺序是 one two three。


      Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
      Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。不会像Promise.resolve那样,根据不同的情况包装Promise
     

    三、扩展和应用

      1、try catch

      try catch是JavaScript的异常处理机制,把可能出错的代码放在try语句块中,如果出错了,就会被catch捕获来处理异常。如果不catch,一旦出错就会造成程序崩溃。

    如果有多个await命令,可以将其都放在try catch结构中,如果执行出错,catch就会捕获异常。

     

      2、ajax:把执行代码和处理结果的代码清晰地分离

      当执行代码有了结果,调用resolve或者reject方法,把结果传递了出去,就可以在后面单独处理结果,两部分代码完全分开,
      可以让代码更加优雅清晰,复用性高,便于维护。
    function ajax(URL) {
        return new Promise(function (resolve, reject) {
            var req = new XMLHttpRequest(); 
            req.open('GET', URL, true);
            req.onload = function () {
            if (req.status === 200) { 
                    resolve(req.responseText);
                } else {
                    reject(new Error(req.statusText));
                } 
            };
            req.onerror = function () {
                reject(new Error(req.statusText));
            };
            req.send(); 
        });
    }
    var URL = "/try/ajax/testpromise.php"; 
    ajax(URL).then(function (value){//如果AJAX成功
        console.log('内容是:' + value); 
    }).catch(function (error){//如果AJAX失败
        console.log('错误:' + error); 
    });

       

      3、状态传递,等待任务

    var p1 = new Promise(function(resolve, reject){
      // ... some code
    });
     
    var p2 = new Promise(function(resolve, reject){
      // ... some code
      resolve(p1);
    })

      上面代码中,p1 和 p2 都是 Promise 的实例,但是 p2 的 resolve 方法将 p1 作为参数,这时 p1 的状态就会传递给 p2。如果调用的时候,p1 的状态是 pending,那么 p2 的回调函数就会等待 p1 的状态改变;如果 p1 的状态已经是 fulfilled 或者 rejected,那么 p2 的回调函数将会立刻执行。

      4、解决回调地狱

      

         function async1() {
                let pro = new Promise(function (resolve, reject) {
                    //做一些异步操作
                    setTimeout(function () {
                        console.log('异步任务1执行完成');
                        resolve('传递结果1');
                    }, 1000);
                });
                return pro;
            }
            function async2() {
                let pro = new Promise(function (resolve, reject) {
                    //做一些异步操作
                    setTimeout(function () {
                        console.log('异步任务2执行完成');
                        resolve('传递结果2');
                    }, 2000);
                });
                return pro;
            }
            function async3() {
                let pro = new Promise(function (resolve, reject) {
                    //做一些异步操作
                    setTimeout(function () {
                        console.log('异步任务3执行完成');
                        resolve('传递结果3');
                    }, 2000);
                });
                return pro;
            }
            async1()
                .then(function (data) {
                    console.log(data);
                    return async2();
                })
                .then(function (data) {
                    console.log(data);
                    return async3();
                })
                .then(function (data) {
                    console.log(data);
                });
    
            // 输出结果:
            // 异步任务1执行完成
            // 传递结果1
            // 异步任务2执行完成
            // 传递结果2
            // 异步任务3执行完成
            // 传递结果3
  • 相关阅读:
    基础数据类型
    python2x与python3x区别(30个)更新中。。。
    注释
    常量
    变量
    十、SpringCloud config分布式配置中心
    九、Gateway新一代网关
    八、Hystrix断路器(下)
    八、Hystrix断路器(上)
    七、OpenFeign服务接口调用
  • 原文地址:https://www.cnblogs.com/jiayouba/p/12041793.html
Copyright © 2020-2023  润新知