当前位置:首页 » JavaScript技术

【前端】主流API-promise解析,js基础。

2017-10-30 16:40 本站整理 浏览(0)

前言

在js领域,promise出现的时间已经很久了,从jquery的$.get().done().fail() 这样的API开始,到现在的es6默认支持的new Promise(),它的出现无疑使异步代码变得信任可靠,更使得前端代码的信任度直线提升,就跟它的名字一样,它的到来,是一个 Promise (保证,允诺)

异步信任问题

有一个朋友在银行系统工作,有一次,我帮他review一段代码,据他所说,这段代码给他带来了一个很大的麻烦,有一位顾客使用这个页面付款的时候意外的提交了两次付款请求,boss复查发现提交的源头是前端,当我接到这段代码的时候,这位朋友已经焦头烂额,我接过了代码, 初步分析,什么也没发现,他的代码就像下面这样:

$("btn").on("click",function(){
                    if($(this).hasClass("no-submit")){
                        $(this).removeClass("no-submit");
                        //忽略
                        $.ajax({
                            url:"....",
                            type:"json",
                            success:function(){
                                submit();
                            }
                        })
                    }
                })

他告诉我,他已经针对按钮做点击唯一提交了,怎么可能调用两次?

但是事实就是有两次submit。

通过分析代码,最后排查的结果,原因是这个jquery的$.ajax是被二次封装过的,某种情况下回调函数被调用了两次。

oh,看起来很简单的原因,这可是个巨坑! 日常代码中,我们都需要接触各种各样的第三方库,谁来保证它们都没有隐藏的问题?如果你遇到了经过加密混淆的库,因为它内部某个try catch,或者内部某个if在非正常情况下发成的误判导致重复调用,我想你就不会再淡定的喝着咖啡敲代码了。

或者你觉得,防止重复调用这个很简单,加个判断就行了:

var a = 0;
                $("btn").on("click",function(){
                    if($(this).hasClass("no-submit")){
                        $(this).removeClass("no-submit");
                        //忽略
                        $.ajax({
                            url:"....",
                            type:"json",
                            success:function(){
                                if( a==0){
                                    submit();
                                }
                                
                            }
                        })
                    }
                })

完美是吧,测试上线,继续喝咖啡。

三个月后。。。。。

有些顾客反应,点击提交后,有时没有提交成功,老板这时大发雷霆,这已经是第二次出现这样的失误了。

是的,只调用一次,但是鬼知道它会不会调用一次?

这里问题的本质是什么?

它就是所谓的回调地域。

我们总是有很多的回调函数需要交给第三方工具处理,类似于$.ajax,jquery就一定是值得信任的?图样图森破!你永远不知道jquery下面可能还埋着上一位开发者给你准备的周年大礼包。最糟糕的是,这个第三方工具你没有修改的能力,要么你换掉他,要么你解密,要么换版本,谁知道作者会不会继续更新?

把我们的回调交给不受信任的第三方程序,这就是回调地域,可怕的很!

Promise调用方式

  今天的主角是promise。它可以让我们的回调变得promise。 

试想,我们把上述的ajax再次封装(在不能改动源代码的情况下),可能代码如下:

 var then = function(resolve,reject,timeout){
                var a = 0;
                $("btn").on("click",function(){
                    if($(this).hasClass("no-submit")){
                        $(this).removeClass("no-submit");
                        //忽略
                        $.ajax({
                            url:"....",
                            type:"json",
                            success:function(){
                                a==0&&resolve();
                                a++;
                            },
                            error:function(){
                               a==0&&reject();
                               a++;
                            }
                        })
                    }
                })
                if(new Date.now()>timeout){
                   a==0&&reject();
                   a++;
                }
            }
            var a = 0;

ok,我只是将前面的代码封装到一个叫then的函数里,这个函数需要三个参数,resolve(解决),reject(拒绝),以及一个超时时间,另外控制调用次数的变量a也有保留。

有什么不同?

不同之处在于,then函数是可信任的,至少现在我们可以确定,它要么被resolve,要么reject,绝不可能出现调用两次,或者不调用的情况。这种封装可以叫做控制反转,

我们将原本给到$.ajax的回调权再次交回自己手中,上面的代码调用起来是这样的:

then(function(){
    submit()
},function(){
    error()
})

ok,我们只解决了其中的一些问题,重复调用,不调用,但还有更多的问题。

让promise登场吧!

使用promise封装上面代码:

 function request(){
            return new Promise(function(resolve,reject){
                $.ajax({
                    url:"....",
                    type:"json",
                    success:function(){
                        resolve()
                    },
                    error:function(){
                       reject()
                    }
                })
            })
        }
       request.then(function(){
            submit();
       },function(){
            error();
       })

ok,是否豁然开朗?

promise就是解决关于异步回调信任,顺便缓解了回调金字塔(一堆回调嵌套在一起,就是一个回调金字塔)问题的解决方案。

在这个Promise构造函数中传入一个函数,这个函数有两个值,resolve、reject。一旦代码请求成功,resolve被调用,决议完成并忽略后面的所有调用。或者失败后决议被拒绝,调用reject。

决议、解决、拒绝术语

  一个promise通常有三种状态  pending(进行中)、fulfilled(已成功)和rejected(已失败)。

可以说在任何情况下,一个promise只有一个决议。 它只可能是fulfilled或者rejected其中一种,并且是不可以逆转的。意思是你无法让一个已经被拒绝的promise回滚。它永远都会有一个确定的回复。

Promise API

  promise.all(Array);

   Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例,并且这个新的promise返回的最终值是传入的多个Promise决议完成的值。

有了Promise.all方法,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加简单易懂。

像这样的调用:

 Promise.all([request("url1"),request("url2")]).then((result)=>{
      console.log(result) //[url1result,url2result]
})

注意传入的顺序,数组promise决议的值与返回的result数组位置是一致的。对于以往的回调方式来说,这是很强大的api。

  Promise.race(array)

这个方法和all调用方式一样,但是决议的方式不一样,.all等待所有决议完成新的 Promise返回决议。 而.race,一旦有某个promise决议完成,其余的会立刻终止。也就是说它只会执行最快的那个promise。

 ...更多api可以查看阮大大的es6教程

结束

promise已经成为主流。我在这里只讲了一些浅见,至于更多的用法,与君共勉!