ECMAScript--Promise

230 阅读5分钟

什么是Promise

是一种异步编程的解决方案。可以理解为一种包装异步操作的方案。就是使得我们的异步操作以一种更优雅的形式进行呈现。使得我们的异步代码更清晰,更明了,更易于维护。也就是说Promise是用来封装我们的异步操作。

异步嵌套(回调地狱)

用一个有点儿夸张的例子,描述一下描述一下Promise的作用。

 setTimeout(function(){
            console.log("a");
            setTimeout(function(){
                console.log("b");
                setTimeout(function(){
                    console.log("c");
                },1000)
            },1000)
        },1000);

三个异步操作的嵌套。这样的代码当然是可以正常运行的。但是它的可读性,可维护性实在是太差了。这时候我们就可以使用Promise来进行包装。

new Promise((resolve,reject)=>{//包装第一个异步操作
                setTimeout(function(){
                    resolve();
                },1000)
            }).then(()=>{//第一个异步操作成功要执行的代码,分离出来了
                console.log("a");
                return new Promise((resolve,reject)=>{//创建一个Promise对象,包装第二个异步操作
                    setTimeout(function(){
                        resolve();
                    },1000)
                })
                
            }).then(()=>{//第二个异步操作成功的代码放在这里
                console.log("b");
                return new Promise((resolve,reject)=>{//创建一个Promise对象,包装第三个异步操作
                    setTimeout(function(){
                        resolve();
                    },1000)
                })
            }).then(()=>{//第三个异步操作成功的代码放在这里
                console.log("c");
            })

Promise包装异步操作的过程

  • 第一步,创建一个Promise对象,他需要传一个参数,这个参数是一个函数并且是固定的:(resolve,reject)=>(),并且resolve和reject也是一个函数。
  • 第二步,将异步操作包装进去,但是将其实际处理的代码分离出来。这个可能不是很好描述。拿数据请求来举例,当请求成功的时候要执行相应操作,在请求失败的时候又要执行相应操作。把这些操作都分离出来。怎么分离呢?通过then方法和catch方法。 这是用Promise包装异步操作的过程描述图。他会存在三个状态,以网络请求举例。
  • 第一个阶段,pending状态,等待阶段。等待请求返回请求结果。
  • 第二个阶段,fulfilled状态,满足阶段。就是请求成功了,这个时候我们会调用resolve函数,调用之后,就会执行.then()中的内容。.then方法的参数也是一个函数()=>{},这个函数的参数和resolve函数一致。一般成功之后,我们需要处理data数据就是下面这样一个过程:
new promise((resolve,reject)=>{
	$.ajax("/url",function(data){
    	resolve(data);//成功调用它并且将data传入。
        reject(err);//失败调用它并将err出入
    })
  }).then(a=>{})//resolve执行完后,立即调用它,并将resolve中的data赋值给a
.catch(b=>{})//reject执行完后,立即调用它, 并将reject中的err赋值给b
  • 第三阶段,reject状态,拒绝状态。就是请求失败了,这时候我们会调用reject函数,调用之后,我们会回调catch方法。它的参数是一个函数()=>{}

Promise的另一种形式

还是以网络请求为例,请求成功和失败的代码就是上文那样的:

new promise((resolve,reject)=>{
	$.ajax("/url",function(data){
    	resolve(data);
        reject(err);
    })
    }).then(data=>{})
    .catch(err=>{})

其实我们可以全部写在then方法中,而不使用catch方法。这时候then方法需要传入两个函数。

new promise((resolve,reject)=>{
	$.ajax("/url",function(data){
    	resolve(data);
        reject(err);
    })
    }).then(data=>{},err=>{})
   

Promise的链式调用

第一种形式,处理异步操作的嵌套(回调地狱)

就是我们前面处理的打印a,b,c的操作,就是Promise的链式调用。

第二种形式,分解then中的操作

描述可能有点抽象。那我们从需求出发,我进行请求,请求成功,接下来我要操作请求来的数据,进行三次处理。我不想将三次处理混在一起。也就是我想分为三个then方法。那这个怎么实现呢?

new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve("a");
                reject("err")
            })
        }).then((res)=>{//第一次操作
            res=res+"b";
            return new Promise(resolve=>{//精髓在这儿
                resolve(res)
            })
        }).then((res)=>{//第二次操作
            res=res+"c";
            return new Promise(resolve=>{
                resolve(res)
            })
        }).then((res)=>{//第三次操作
            res=res+"d";
            console.log(res);
        })

比如说我这,我简单模拟了一下,我的需求是把"a"拼接成'abcd'。我把每次拼接都拆分成一次操作。这里是怎么实现then方法的连用呢?
如果要实现then方法的连用,那么每次then方法执行的返回值应该是一个Promise对象。因为then方法是Promise的方法只能Promise对象才能调用。并且返回的这个Promise对象有点特殊,他要进入下个then方法的话,必然是要执行resolve方法。对吧。其实这里reject没有存在的意义。(本身new Promise((resolve,reject)=>{})中的reject就是一个可选参数!!)那么这里我们直接省略reject参数。就形成了我们上文的结构

前面形式的语法糖写法

new Promise(resolve=>{resolve(data)}).then((data)=>{})

这种写法等于

 Promise.resolve(data).then((data)=>{})

那上面的代码可以改写为:

       new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve("a");
                reject("err")
            })
        }).then((res)=>{
            res=res+"b";
            return Promise.resolve(res)
        }).then((res)=>{
            res=res+"c";
            return Promise.resolve(res)
        }).then((res)=>{
            res=res+"d";
            console.log(res);
        })

还有一种更简洁的写法:直接return 其实内部做的事儿应该是一样的。只是一种语法糖。

   new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve("a");
                reject("err")
            })
        }).then((res)=>{
            res=res+"b";
            return res
        }).then((res)=>{
            res=res+"c";
            return res
        }).then((res)=>{
            res=res+"d";
            console.log(res);
        })

这三段代码是一模一样的。可以在console控制台运行查看结果。

抛出异常的简写

另外其实抛出错误也一种简洁写法。

   new Promise((resolve,reject)=>{
            reject("err");
        })

这种形式是不是一定抛出异常呢?他也有种简写。

   Promise.reject("err")

好了,关于Promise的知识就涉及到这儿, 以后我会继续补充的。加油学习0.0

Promise的all用法

还是从需求出发理解每个用法的作用。
比如我现在得进行两次网络请求,我得等到两次网络请求都请求到再来处理请求结果该怎么实现呢? Promise提供了一种all方法。

Promise.all(iteator)

iterator就是之前我在ES6文章中提到的可迭代对象。这里可以看成数组,数组的元素都是用Promise封装的异步操作

  Promise.all([
            new Promise((resolve,reject)=>{
            setTimeout(() => {
                resolve("a");
            }, 1000);
        }),new Promise((resolve,reject)=>{
            setTimeout(() => {
                resolve('b');
            }, 5000);
        })]).then(res=>console.log(res))

也就是说当两个异步操作都执行resolve方法的时候,也就是都处于fulfilled状态的时候,才会执行then方法。并且这里的then方法的res是一个数组,数组的元素是由每个resolve函数的实参组成的。所以这里应该是['a','b']