promise解析,js基础。
发布于 6 年前 作者 294678380 8269 次浏览 来自 分享

此文章同步发表在我的博客:我的博客

前言

在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已经成为主流。我在这里只讲了一些浅见,至于更多的用法,与君共勉!

12 回复

时代在进步,async await已然成为主流

@lpbird 似乎async返回的是promise对象吧

是的,只是看到文中的示例,介绍更加简洁,更容易错误处理的es7 会更好

你们都不用async.auto吗? 这个也很好用啊

@lpbird 很多简单的场景用 promise 不需要额外定义变量,用 await 反而造成了沉重的封装。 而到了一些复杂的异步流程控制,也是直接用 promise 更方便。相辅相成的。

@leavesdrift 如果一个流程有几个异步流程,而在这个流程里面才去做promise的封装,只会让代码变得臃肿而已。 而且前端的ajax库早有不少promise版了,根本不需要自己去封装。 额外变量这个也是,直接用promise你需要定义回调函数和形参,这不也是额外变量吗。

@leavesdrift 当有多个联系紧密的异步操作,一个异步操作做完接着下一个这种,封装在一个async方法比链式调用then简洁的

来自酷炫的 CNodeMD

这种文章很多了

@q86002618 你写下吧我试试 promise 写法会不会更简洁,只是在我看来 promise 足够应付大部分场景了,async/await 的包装很重而且很多时候需要定义变量写起来不太适应吧。

@jokerapi 因为 promise 有很多很棒的写法,所以我一直比较崇尚 promise,async/await 写起来比较无味,只能说 promise 已经足够应付绝大多数场景了,在异步流程控制的时候写起来也比较有技巧,会很好玩,我当然也用 async/await ,只是说我觉得不能放弃 promise 那种花式,很有技巧的写法。因为 promise 的玩法确实多样。

@leavesdrift 手机上不方便😅

来自酷炫的 CNodeMD

回到顶部