JS异步 promise简单了解

最近有ajax之类异步编程需求,整理下promise的使用。

背景介绍

  Js是单线程语言,为了避免耗时任务阻塞代码,对耗时长的任务采用异步模式调用。前一个任务执行完,调用回调函数而不是进行后一个任务。后一个任务不等前一个任务结束就执行,任务调用的顺序和排列顺序不同。
实现异步和后续处理最原始的方法就是使用回调函数,在耗时任务结束时调用后续操作。
示例:

function f(callback){
    //setTimeout模拟耗时操作
    setTimeout(function(){
            console.log("test")
            callback()
        },1000)
}

function f2(){
    console.log("callback")
}

f(f2)

  示例中简单把函数f2作为回调函数传入函数f中,利用f(f2)实现异步回调操作。但回调方法一旦变得复杂,就会出现多重回调的情况,代码容易变得庞大复杂。比如项目中用到不同的接口进行ajax调用,如下面示例:

$.ajax({
    //请求获取场地信息
    url: '/position',
    success (result){
        let pId = result.filter(
            item => {
                if(item.posName == 'north'){
                    return item
                }
            }
        )[0].posId      
        //请求获取场地对应的渔场信息
        $.ajax({
            url: '/place?pid=' + pId,
            success (result){
                //获取该场地第一块渔场信息
                let placeId = result[0].placeId
                $.ajax({
                    //获取对应渔场鱼种信息
                    url: '/fish?pid=' + pId +"&placeid=" + placeId,
                    success (result){
                        result.forEach(element => {
                            console.log(element.name)
                        });
                    }
                })
            }
        })
    }
})

  多重回调导致代码可读性非常差,为了更方便地进行异步编程地实现,ES6中实现了promise对象来解决问题。

promise对象介绍

  Promise是一个对象,它支持函数return,异步操作不需要再层层传递回调函数进行回,同时能合并多个异步等待操作,便于处理。
  Promise有状态的概念,其有三种状态:pending(运行),resolved(成功)和rejected(失败)。状态一经改变则不会再改变。改变的模式有两种,分别为pending->resolved和pending->rejected。简单来说,promise就是能用同步方式写异步代码。
  Promise对象的构造函数参数为执行任务操作的函数,简单示例:

 var p = new Promise(
        function(resolve, reject){
            console.log("start")
            setTimeout(
                function(){
                    console.log("step 1")
                    resolve("step 1 complete")
                },1000
            )
        }

  可以看到,参数函数中包括两个参数,分别为resolve(fulfille)和reject方法。当调用resolve方法后,该promise对象的状态就变为resolved。同样,当调用reject方法后,该promise对象的状态变为rejected。
  Promise中的常用方法有then,catch,all,race等,下面来分别讲下作用。

then

  Then()用来形成promise队列,是promise实现异步操作同步写法的重要手段。
  首先来看下这几个函数:

function start(){
    var p = new Promise(
        function(resolve, reject){
            console.log("start")
            setTimeout(
                function(){
                    console.log("step 1")
                    resolve("step 1 complete")
                },1000
            )
        }
    )
    return p
}

function second(result){
    console.log(result)
    var p = new Promise(
        function(resolve, reject){
            setTimeout(
                function(){
                    console.log("step 2")
                    resolve("step 2 complete")
                },1000
            )
        }
    )
    return p
}

function end(result){
    console.log(result)
    var p = new Promise(
        function(resolve, reject){
            setTimeout(
                function(){
                    console.log("all step complete")
                    resolve("end")
                },1000
            )
        }
    )
    return p
}

  可以看到,每个函数中都有一个延时异步操作的promise,并返回对应的promise对象。那么如果需要将这三个操作连接使用,start->second->end顺序来执行,就需要使用promise的then方法进行处理。

function promise1(){
    start()
        .then(second)
        .then(end)
        .then(function(result){
            console.log(result)
        })
}

  当利用then形成promise链时,前一个promise调用会传递一个promise对象到后续的then中。Then可以有两个参数,一个为resolve方法,一个为reject方法。这两个方法调用哪个,由之前传递过来的promise的状态指定。promise中调用resolve或reject方法时传递的参数,会作为下一级then中调用函数的参数来使用。
  在上述示例中,start中return promise对象,那么该promise执行resolve方法后,状态改变,自身被传递到下一个then中执行resolve方法,也就是second。以此类推,最终输出所有信息顺序为:

Start
Step1
Step1complete
Step2
Step2complete
all step complete
end

  上面的函数都传递了promise对象给下一级then,那么如果return别的值呢?
  如果return的值为promise对象,那么下一级then会在其状态发生改变后执行对应的方法。
  如果return的值为其它对象,那么下一级then则会立即执行,同时遵循的promise状态为上一个存在的被传递过来的promise的状态,并一直传递下去,直到函数返回新的promise对象。

  对于直接执行then的理解,可以看下面这个示例:

function f_1(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function() {
            console.log("first")
            resolve("second");
          }, 1000);
    })
    return p
}

f_1()
    .then(function(data){
        setTimeout(function(){
            console.log(data)
        },1000)
        return "end"
    })
    .then(function(data){
        console.log(data)
    })

该示例的输出结果为:
first
end
second

  分析一下,第一个promise延时方法输出first执行resolve改变状态后,下一级then执行函数,而此时,then中函数返回的不是promise,那么再下一级then就立刻执行,输出end。最后前一个then中延时方法执行,输出second。
  这个例子很好地说明了两种不同返回情况对于后续then的执行的差异,看懂了基本就没问题了。

catch

  catch主要有两种使用方法:用来代替reject的回调和捕获异常并处理。
  代替reject回调的示例:

function promise4(){
    var p = new Promise(
        function(resolve, reject){
            setTimeout(
                function(){
                    reject("rejected")
                },1000
            )
        }
    )
    p
        .then((result => {console.log("good1")}))
        .catch((() => {console.log("bad2")}))
}

  示例最终输出为bad2。可以看到,then中没有指定reject的方法,调用了catch中方法进行处理。

  捕获异常的演示:

function promise5(){
    var p = new Promise(
        function(resolve, reject){
            setTimeout(
                function(){
                    resolve("good")
                },1000
            )
        }
    )
    p
        .then((result => {
            throw new Error("no good")
            console.log("good1")
        }))
        .catch((result => {console.log(result)}))
}

  输出为
  Error: no good
  at promise_test.js:137
  成功捕获到异常

  需要注意的是,在默认情况下,catch会返回一个resolved状态的promise对象。也就是说,如果catch后还有下一级then,它也会正常进行操作。而当catch中也抛出错误时,会直接定位到链中的下一个catch执行,略过中间的操作。

all

  All的使用也是promise的一大创新点,它能同时进行数个promise的执行,并且将所有的返回通过数组形式整合后返回。形式为Promise.all([p1, p2, p3])

示例:

function a_1(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function() {
              resolve("a1");
            }, 1000);
    })
     return p
}
function a_2(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function() {
            resolve("a2");
            }, 1000);
    })
    return p
}

Promise
    .all([a_1(), a_2()])
.then(result => console.log(result))

  输出为[“a1”,”a2”]。
  All的调用会在所有异步操作结束后返回。

race

  race方法使用和all差不多,但是该方法在任意一个延时操作完成后就返回,所以将有时间限制的方法和计时器进行绑定,进行超时提示。

resolve和reject

  这两个为Promise的静态方法,分别用来返回resolved状态的promise实例和reject状态的promise实例。参数value作为对应的返回值。
  其中resolved方法的参数也可为promise对象,此时就返回该对象。为拥有函数then的thenable对象时,返回的对象状态由这个thenable对象决定。
  之前的示例中,我们都是由一个返回promise对象的函数起手,有了这两个方法就可以直接获得想要的状态的promise对象,很方便。

总结

  讲到这里,前面背景中提出的多重回调方法就能完美解决了。在实际项目中,ajax的调用和promise结合后就会方便很多。同时all,race等,解决了超时和同时异步操作的问题。ES7中增加的async/await关键词,个人认为就是对promise的一种编写方法上的延展,让异步代码看起来更像同步代码,理解起来反而增加难度,个人不太喜欢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值