这次聊聊Promise对象

Promise是CommonJS提出的一种规范,在ES6中已经原生支持Promise对象,非ES6环境可以用Bluebird等库来支持。

0.引入

在js中任务的执行模型有两种:同步模式和异步模式。

同步模式:后一个任务B等待前一个任务A结束后,再执行。任务的执行顺序和任务的排序顺序是一致的。

异步模式:每一个任务有一个或多个回调函数,前一个任务A结束后,不是执行后一个任务B,而是执行任务A的回调函数。而后一个任务B是不等任务A结束就执行。任务的执行顺序,与任务的排序顺序不一致。

异步模式编程有四种方法:回调函数(最基本的方法,把B写成A的回调函数)、事件监听(为A绑定事件,当A发生某个事件,就执行B)、发布/订阅,以及本文要介绍的Promise对象。

Promise是一个用于处理异步操作的对象,可以将回调函数写成链式调用的写法,让代码更优雅、流程更加清晰,让我们可以更合理、更规范地进行异步处理操作。它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。

1.Promise的基本知识

1.1 三种状态

Pending:进行中,刚创建一个Promise实例时,表示初始状态;

resolved(fulfilled):resolve方法调用的时候,表示操作成功,已经完成;

Rejected:reject方法调用的时候,表示操作失败;

1.2 两个过程

这三种状态只能从pendeng-->resolved(fulfilled),或者pending-->rejected,不能逆向转换,也不能在resolved(fulfilled)和rejected之间转换。并且一旦状态改变,就不会再改变,会一直保持这个结果。

汇总上述,创建一个Promise的实例是这样的:

//创建promise的实例
let promise = new Promise((resolve,reject)=>{
    //刚创建实例时的状态:pending

    if('异步操作成功'){
        //调用resolve方法,状态从pending变为fulfilled
        resolve();
    }else{
        //调用reject方法,状态从pending变为rejected
        reject();
    }
});

1.3 then()

用于绑定处理操作后的处理程序,分别指定fulfilled状态和rejected状态的回调函数,即它的参数是两个函数,第一个用于处理操作成功后的业务,第二个用于处理操作失败后的业务。

//then()
promise.then((res)=> {
    //处理操作成功后的业务(即Promise对象的状态变为fullfilled时调用)
},(error)=> {
    //处理操作失败后的业务(即Promise对象的状态变为rejected时调用)
});

1.4 catch()

用于处理操作异常的程序,catch()只接受一个参数

//catch()
promise.catch((error)=> {
    //处理操作失败后的业务
});

一般来说,建议不要在then()里面定义rejected状态的回调函数,而是将then()用于处理操作成功,将catch()用于处理操作异常。因为这样做可以捕获then()执行中的错误,也更接近同步中try/catch的写法:

//try-catch
// bad
promise.then((res)=> {
    //处理操作成功后的业务
  }, (error)=> {
    //处理操作失败后的业务
  });

// good
promise
  .then((res)=> { 
    //处理操作成功后的业务
  })
  .catch((error)=> {
    //处理操作失败后的业务
  });

1.5 all()

接受一个数组作为参数,数组的元素是Promise实例对象。只有当参数中的实例对象的状态都为fulfilled时,Promise.all( )才会有返回。

实例代码(可直接在浏览器中打开):

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Promise实例</title>
    <style type="text/css"></style>
    <script type="text/javascript">
        window.onload = () => {
            //创建实例promise1
            let promise1 = new Promise((resolve) => {
                setTimeout(() => {
                    resolve('promise1操作成功');
                    console.log('1')
                }, 3000);
            });

            //创建实例promise1
            let promise2 = new Promise((resolve) => {
                setTimeout(() => {
                    resolve('promise1操作成功');
                    console.log('2')
                }, 1000);
            });


            Promise.all([promise1, promise2]).then((result) => {
                console.log(result);
            });
        }
    </script>
</head>

<body>
    <div></div>
</body>

</html>

结果(注意看时间):

Promise.all()

代码说明:

1s后,promise2进入fulfilled状态,间隔2s,也就是3s后,promise1也进入fulfilled状态。这时,由于两个实例都进入了fulfilled状态,所以Promise.all()才进入了then方法。

使用场景:执行某个操作需要依赖多个接口请求回的数据,且这些接口之间不存在互相依赖的关系。这时使用Promise.all(),等到所有接口都请求成功了,它才会进行操作。

1.6 race()

和all()的参数一样,参数中的promise实例,只要有一个状态发生变化(不管是成功fulfilled还是异常rejected),它就会有返回,其他实例中再发生变化,它也不管了。

实例代码(可直接在浏览器中打开):

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Promise实例</title>
    <style type="text/css"></style>
    <script type="text/javascript">
        window.onload = () => {
            //创建实例promise1
            let promise1 = new Promise((resolve) => {
                setTimeout(() => {
                    resolve('promise1操作成功');
                    console.log('1')
                }, 3000);
            });

            //创建实例promise1
            let promise2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    reject('promise1操作失败');
                    console.log('2')
                }, 1000);
            });


            Promise.race([promise1, promise2])
                .then((result) => {
                    console.log(result);
                })
                .catch((error) => {
                    console.log(error);
                })
        }
    </script>
</head>

<body>
    <div></div>
</body>

</html>

结果(注意看时间):

Promise.race()

代码说明:

1s后,promise2进入rejected状态,由于一个实例的状态发生了变化,所以Promise.race()就立刻执行了。

2 实例

平时开发中可能经常会遇到的问题是,要用ajax进行多次请求。例如现在有三个请求,请求A、请求B、请求C。请求C要将请求B的请求回来的数据做为参数,请求B要将请求A的请求回来的数据做为参数。

按照这个思路,我们可能会直接写出这样的层层嵌套的代码:

//------请求A 开始---------
    $.ajax({
        success:function(res1){


            //------请求B 开始----
            $.ajax({
                success:function(res2){


                    //----请求C 开始---
                    $.ajax({
                        success:function(res3){
                        }
                    });
                    //---请求C 结束---


                }    
            });
            //------请求B 结束-----


        }
    });
    //------请求A 结束---------

在请求A的success后,请求B发送请求,在请求B 的success后,请求C发送请求。请求C结束后,再向上到请求B结束,请求B结束后,再向上到请求A结束。

这样虽然可以完成任务,但是代码层层嵌套,代码可读性差,也不便于调试和后续的代码维护。而如果用Promise,你可以这样写(示意代码,无ajax请求):

此处附上完整可执行代码,可在浏览器的控制台中查看执行结果:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Promise实例</title>
    <style type="text/css"></style>
    <script type="text/javascript">
        window.onload = () => {
            let promise = new Promise((resolve, reject) => {

                if (true) {
                    //调用操作成功方法
                    resolve('操作成功');
                } else {
                    //调用操作异常方法
                    reject('操作异常');
                }
            });

            //then处理操作成功,catch处理操作异常
            promise.then(requestA)
                .then(requestB)
                .then(requestC)
                .catch(requestError);

            function requestA() {
                console.log('请求A成功');
                return '下一个是请求B';
            }
            function requestB(res) {
                console.log('上一步的结果:' + res);
                console.log('请求B成功');
                return '下一个是请求C';
            }
            function requestC(res) {
                console.log('上一步的结果:' + res);
                console.log('请求C成功');
            }
            function requestError() {
                console.log('请求失败');
            }
        }
    </script>
</head>

<body>
    <div></div>
</body>

</html>

结果如下:

实例

可以看出请求C依赖请求B的结果,请求B依赖请求A的结果,在请求A中是使用了return将需要的数据返回,传递给下一个then()中的请求B,实现了参数的传递。同理,请求B中也是用了return,将参数传递给了请求C。

3.小结

本文主要介绍了Promise对象的三个状态和两个过程。“三个状态”是:初始化、操作成功、操作异常,“两个过程”是初始化状态到操作成功状态,和初始化状态到操作异常状态。除此之前,还有两种实例方法:then()、catch()来绑定处理程序。类方法:Promise.all()、Promise.race()。如有问题,欢迎指正。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

1 条评论
登录 后参与评论

相关文章

来自专栏技术博客

.Net 4.5 异步编程初试(async和await)

  最近自己在研究Asp.Net Web API。在看到通过客户端来调用Web API的时候,看到了其中的异步编程,由于自己之前没有接触过,所以就稍微的学习了解...

873
来自专栏

c++ 之bind使用

网络编程中, 经常要使用到回调函数。 当底层的网络框架有数据过来时,往往通过回调函数来通知业务层。 这样可以使网络层只专注于 数据的收发, 而不必关心业务 在c...

3043
来自专栏从零开始学 Web 前端

04 - JavaSE之异常处理

2.throw new someExpresion("错误原因"); 表示的是手动抛出异常。 **

1044
来自专栏猿天地

java8 Lambda尝尝鲜

转载:猿天地 链接:http://cxytiandi.com/blog/detail/2196 java8都已经发布这么久了,一直没来得及使用,线上环境基本...

33711
来自专栏我爱编程

Day8面向对象编程1/2

关于以下程序的笔记: Python函数式编程2/3 def count(): fs = [] for i in range(1,4): ...

2638
来自专栏java一日一条

10个经典的 Java main 方法面试题

在Java 7之前,你可以通过使用静态初始化运行Java类。但是,从Java 7开始就行不通了。

421
来自专栏Python小屋

Python面向对象程序设计中属性的作用与用法

公开的数据成员可以在外部随意访问和修改,很难保证用户进行修改时提供新数据的合法性,数据很容易被破坏,并且也不符合类的封装性要求。解决这一问题的常用方法是定义私有...

2314
来自专栏木木玲

设计模式 ——— 代理模式

1294
来自专栏lgp20151222

Class.forName()用法详解

主要功能 Class.forName(xxx.xx.xx)返回的是一个类 Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,...

691
来自专栏Java技术分享

反射类的main方法

有时候我们需要调用一个类的Main方法,也可说是执行这个类的代码。但是这时候这个类我们还没有写好,或者这个类是通过网络运行时传给我们的,我们就不可能在程序中知道...

1806

扫码关注云+社区