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

 

posted @ 2020-09-18 09:50  飞叶飞花  阅读(580)  评论(0编辑  收藏  举报