zoukankan      html  css  js  c++  java
  • 彻底理解Javascript 中的 Promise(-------------------------------***---------------------------------)

    ES6原生提供了 Promise 对象。

    到底是何方妖怪呢?打出来看看:

    所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

    Promise 对象有以下两个特点。

    (1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。

    (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

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

    Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

    废话不多说,直接上demo:

    <!DOCTYPE html>
    <html>
    
        <head>
            <meta charset="utf-8" />
            <title>Promise 学习笔记</title>
            <script type="text/javascript">
                window.onload = function() {
                    function pms1() {
                        return new Promise(function(resolve, reject) {
                            setTimeout(function() {
                                console.log('执行任务1');
                                resolve('执行任务1成功');
                            }, 2000);
                        });
                    }
    
                    function pms2() {
                        return new Promise(function(resolve, reject) {
                            setTimeout(function() {
                                console.log('执行任务2');
                                resolve('执行任务2成功');
                            }, 2000);
                        });
                    }
    
                    function pms3() {
                        return new Promise(function(resolve, reject) {
                            setTimeout(function() {
                                console.log('执行任务3');
                                resolve('执行任务3成功');
                            }, 2000);
                        });
                    }
                    pms1().then(function(data) {
                            console.log('第1个回调:' + data);
                            return pms2();
                        })
                        .then(function(data) {
                            console.log('第2个回调:' + data);
                            return pms3();
                        })
                        .then(function(data) {
                            console.log('第3个回调:' + data);
                            return '还没完!该结束了吧!'
                        }).then(function(data) {
                            console.log(data);
                        });
                }
            </script>
        </head>
    
        <body>
    
        </body>
    
    </html>

    怎么样?是不是灰常简单啊!

    demo2:

    <!DOCTYPE html>
    <html>
    
        <head>
            <meta charset="UTF-8">
            <title></title>
            <script type="text/javascript">
                window.onload = function() {
                    function pms1() {
                        return new Promise(function(resolve, reject) {
                            setTimeout(function() {
                                var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
                                if(num <= 5) {
                                    resolve(num);
                                } else {
                                    reject('数字太大了吧!');
                                }
                            }, 2000);
                        });
                    }
                    setInterval(function() {
                        pms1().then(function(data) {    //小于等于5的
                            console.log(data);
                        }, function(data) {     //大于的
                            console.log(data);
                        })
                    }, 1000);
                }
            </script>
        </head>
    
        <body>
        </body>
    
    </html>

    Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法。

    如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从「未完成」变为「成功」(即从 pending 变为 resolved);

    如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从「未完成」变为「失败」(即从 pending 变为 rejected)。

    all的用法:

    demo:

    <!DOCTYPE html>
    <html>
    
        <head>
            <meta charset="utf-8" />
            <title>Promise 学习笔记</title>
            <script type="text/javascript">
                window.onload = function() {
                    function pms1() {
                        return new Promise(function(resolve, reject) {
                            setTimeout(function() {
                                console.log('执行任务1');
                                resolve('执行任务1成功');
                            }, 2000);
                        });
                    }
    
                    function pms2() {
                        return new Promise(function(resolve, reject) {
                            setTimeout(function() {
                                console.log('执行任务2');
                                resolve('执行任务2成功');
                            }, 2000);
                        });
                    }
    
                    function pms3() {
                        return new Promise(function(resolve, reject) {
                            setTimeout(function() {
                                console.log('执行任务3');
                                resolve('执行任务3成功');
                            }, 2000);
                        });
                    }
                    Promise.all([pms1(), pms2(), pms3()]).then(function(data) {
                        console.log(data);
                        console.log({}.toString.call(data));
                    })
                }
            </script>
        </head>
    
        <body>
    
        </body>
    
    </html>

    用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操作返回的数据哪里去了呢?都在then里面呢,all会把所有异步操作的结果放进一个数组中传给then,就是上面的results。

     

     

    race的用法

    all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样,我们把上面runAsync1的延时改为1秒来看一下:

     

    <!DOCTYPE html>
    <html>
    
        <head>
            <meta charset="utf-8" />
            <title>Promise 学习笔记</title>
            <script type="text/javascript">
                window.onload = function() {
                    function pms1() {
                        return new Promise(function(resolve, reject) {
                            setTimeout(function() {
                                console.log('执行任务1');
                                resolve('执行任务1成功');
                            }, 1000);
                        });
                    }
    
                    function pms2() {
                        return new Promise(function(resolve, reject) {
                            setTimeout(function() {
                                console.log('执行任务2');
                                resolve('执行任务2成功');
                            }, 2000);
                        });
                    }
    
                    function pms3() {
                        return new Promise(function(resolve, reject) {
                            setTimeout(function() {
                                console.log('执行任务3');
                                resolve('执行任务3成功');
                            }, 3000);
                        });
                    }
                    Promise.race([pms1(), pms2(), pms3()]).then(function(data) {
                        console.log(data);   //注意上面的延时时间
                    })
                }
            </script>
        </head>
    
        <body>
    
        </body>
    
    </html>

    看到没:只有第一个执行了回调!
    在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。
     
    这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作。

     再来看看jquery里面的$.Deferred:

            jquery用$.Deferred实现了Promise规范,$.Deferred是个什么玩意呢?还是老方法,打印出来看看,先有个直观印象:
    var def = $.Deferred();
    console.log(def);
    输出如下:
    $.Deferred()返回一个对象,我们可以称之为Deferred对象,上面挂着一些熟悉的方法如:done、fail、then等。jquery就是用这个Deferred对象来注册异步操作的回调函数,修改并传递异步操作的状态。
     
    Deferred对象的基本用法如下,为了不与ajax混淆,我们依旧举setTimeout的例子:
    <!doctype html>
    <html lang="en">
    
        <head>
            <meta charset="UTF-8" />
            <title>Document</title>
            <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
            <script type="text/javascript">
                $(function() {
                    function runAsync() {
                        var def = $.Deferred();
                        setTimeout(function() {
                            console.log('执行完成');
                            def.resolve('随便什么数据');
                        }, 2000);
                        return def;
                    }
                    runAsync().then(function(data) {
                        console.log(data)
                    });
                })
            </script>
        </head>
    
        <body>
    
        </body>
    
    </html>

    在runAsync函数中,我们首先定义了一个def对象,然后进行一个延时操作,在2秒后调用def.resolve(),最后把def作为函数的返回。调用runAsync的时候将返回def对象,然后我们就可以.then来执行回调函数。

    是不是感觉和ES6的Promise很像呢?

    区别在何处一看便知。由于jquery的def对象本身就有resolve方法,所以我们在创建def对象的时候并未像ES6这样传入了一个函数参数,是空的。在后面可以直接def.resolve()这样调用。
     
    <!doctype html>
    <html lang="en">
    
        <head>
            <meta charset="UTF-8" />
            <title>Document</title>
            <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
            <script type="text/javascript">
                $(function() {
                    function runAsync() {
                        var def = $.Deferred();
                        setTimeout(function() {
                            console.log('执行完成');
                            def.resolve('随便什么数据');
                        }, 2000);
                        return def;
                    }
                    var pms=runAsync();
                    pms.then(function(data) {
                        console.log(data)
                    });
                    pms.resolve('我穿越了!')
                })
            </script>
        </head>
    
        <body>
    
        </body>
    
    </html>

    这样也有一个弊端,因为执行runAsync()可以拿到def对象,而def对象上又有resolve方法,那么岂不是可以在外部就修改def的状态了?比如我把上面的代码修改如下:
    现象会如何呢?并不会在2秒后输出“执行完成”,而是直接输出“我穿越了”。因为我们在异步操作执行完成之前,没等他自己resolve,就在外部给resolve了。这显然是有风险的,比如你定义的一个异步操作并指定好回调函数,有可能被别人给提前结束掉,你的回调函数也就不能执行了。
     
    怎么办?jquery提供了一个promise方法,就在def对象上,他可以返回一个受限的Deferred对象,所谓受限就是没有resolve、reject等方法,无法从外部来改变他的状态,用法如下:
    <!doctype html>
    <html lang="en">
    
        <head>
            <meta charset="UTF-8" />
            <title>Document</title>
            <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
            <script type="text/javascript">
                $(function() {
                    function runAsync() {
                        var def = $.Deferred();
                        setTimeout(function() {
                            console.log('执行完成');
                            def.resolve('随便什么数据');
                        }, 2000);
                        return def.promise();
                    }
                    var pms=runAsync();
                    pms.then(function(data) {
                        console.log(data)
                    });
                    //pms.resolve('我穿越了!');    //这一句会报错jquery-3.1.1.min.js:2 Uncaught TypeError: pms.resolve is not a function
                })
            </script>
        </head>
    
        <body>
    
        </body>
    
    </html>

     

    then的链式调用

    既然Deferred也是Promise规范的实现者,那么其他特性也必须是支持的。链式调用的用法如下:
    <!doctype html>
    <html lang="en">
    
        <head>
            <meta charset="UTF-8" />
            <title>Document</title>
            <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
            <script type="text/javascript">
                $(function() {
                    function runAsync() {
                        var def = $.Deferred();
                        setTimeout(function() {
                            console.log('执行完成');
                            def.resolve('随便什么数据');
                        }, 2000);
                        return def.promise();
                    }
                    var pms = runAsync();
    
                    pms.then(function(data) {
                            console.log('1:' + data);
                            return runAsync();
                        })
                        .then(function(data) {
                            console.log('2:' + data);
                            return runAsync();
                        })
                        .then(function(data) {
                            console.log('3:' + data);
                        });
                    //pms.resolve('我穿越了!');    //这一句会报错jquery-3.1.1.min.js:2 Uncaught TypeError: pms.resolve is not a function
                })
            </script>
        </head>
    
        <body>
    
        </body>
    
    </html>

    done与fail

    我们知道,Promise规范中,then方法接受两个参数,分别是执行完成和执行失败的回调,而jquery中进行了增强,还可以接受第三个参数,就是在pending状态时的回调,如下:
    deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
    除此之外,jquery还增加了两个语法糖方法,done和fail,分别用来指定执行完成和执行失败的回调,也就是说这段代码:
    d.then(function(){
        console.log('执行完成');
    }, function(){
        console.log('执行失败');
    });
    与这段代码是等价的:
    d.done(function(){
        console.log('执行完成');
    })
    .fail(function(){
        console.log('执行失败');
    });
    

    always的用法

    jquery的Deferred对象上还有一个always方法,不论执行完成还是执行失败,always都会执行,有点类似ajax中的complete。不赘述了。
     

    $.when的用法

    jquery中,还有一个$.when方法来实现Promise,与ES6中的all方法功能一样,并行执行异步操作,在所有的异步操作执行完后才执行回调函数。不过$.when并没有定义在$.Deferred中,看名字就知道,$.when,它是一个单独的方法。与ES6的all的参数稍有区别,它接受的并不是数组,而是多个Deferred对象,如下:
    $.when(runAsync(), runAsync2(), runAsync3())
    .then(function(data1, data2, data3){
        console.log('全部执行完成');
        console.log(data1, data2, data3);
    });
    jquery中没有像ES6中的race方法吗?就是以跑的快的为准的那个方法。对的,jquery中没有。
    以上就是jquery中Deferred对象的常用方法了,还有一些其他的方法用的也不多,干脆就不记它了。接下来该说说ajax了。
     

    ajax与Deferred的关系

    jquery的ajax返回一个受限的Deferred对象,还记得受限的Deferred对象吧,也就是没有resolve方法和reject方法,不能从外部改变状态。想想也是,你发一个ajax请求,别人从其他地方给你取消掉了,也是受不了的。
     
    既然是Deferred对象,那么我们上面讲到的所有特性,ajax也都是可以用的。比如链式调用,连续发送多个请求:
    req1 = function(){
        return $.ajax(/*...*/);
    }
    req2 = function(){
        return $.ajax(/*...*/);
    }
    req3 = function(){
        return $.ajax(/*...*/);
    }
    
    req1().then(req2).then(req3).done(function(){
        console.log('请求发送完毕');
    });
    

     

    明白了ajax返回对象的实质,那我们用起来就得心应手了。
     

    success、error与complete

    这三个方法或许是我们用的最多的,使用起来是这样的:
    $.ajax(/*...*/)
    .success(function(){/*...*/})
    .error(function(){/*...*/})
    .complete(function(){/*...*/})
    分别表示ajax请求成功、失败、结束的回调。这三个方法与Deferred又是什么关系呢?其实就是语法糖,success对应done,error对应fail,complete对应always,就这样,只是为了与ajax的参数名字上保持一致而已,更方便大家记忆,看一眼源码:
    deferred.promise( jqXHR ).complete = completeDeferred.add;
    jqXHR.success = jqXHR.done;
    jqXHR.error = jqXHR.fail;
    complete那一行那么写,是为了减少重复代码,其实就是把done和fail又调用一次,与always中的代码一样。deferred.promise( jqXHR )这句也能看出,ajax返回的是受限的Deferred对象。
     
    jquery加了这么些个语法糖,虽然上手门槛更低了,但是却造成了一定程度的混淆。一些人虽然这么写了很久,却一直不知道其中的原理,在面试的时候只能答出一些皮毛,这是很不好的。这也是我写这篇文章的缘由。
     
     
     
     
    看一个promise.js库:
    /*!
     * Promise JavaScript Library v2.0.0
     */
    ;
    (function(window) {
        var _promise = function(thens) {
            this.thens = thens || [];
            this.state = "";
    
            this._CONSTANT = {
                any: "any",
                number: "number",
                resolved: "resolved",
                rejected: "rejected",
                pending: "pending"
            };
        };
    
        _promise.prototype = {
            resolve: function() {
                if(this.state == this._CONSTANT.pending) {
                    this.state = this._CONSTANT.resolved;
                    return;
                }
                if(this.state !== "") return;
                if(this.promiseArr) {
                    for(var i = 0, j = this.promiseArr.length; i < j; i++) {
                        this.promiseArr[i].resolveCount++;
                    }
                    if(this.promiseArr[0].action !== this._CONSTANT.any) {
                        if(this.resolveCount !== this.promiseArr.length) {
                            return;
                        }
                    } else {
                        if(this.resolveCount > 1) {
                            return;
                        }
                    }
                }
                this.state = this._CONSTANT.resolved;
                if(!this.thens) return;
                if(this.thens[0] && this.thens[0].finallyCB) this.thens[0].finallyCB.apply(null, arguments);
                var t, n;
                while(t = this.thens.shift()) {
                    if(typeof t === this._CONSTANT.number) {
                        var self = this;
                        setTimeout(function() {
                            var prms = new _promise(self.thens);
                            prms.resolve();
                        }, t);
                        break;
                    }
                    var doneFn = t.done,
                        action = t.action;
                    if(!doneFn) continue;
                    if(doneFn instanceof Array) {
                        var arr = [];
                        for(var i = 0, j = doneFn.length; i < j; i++) {
                            var df = doneFn[i];
                            if(df instanceof _promise) {
                                df.thens = this.thens;
                                arr.push(df);
                            } else {
                                var m = df.apply(null, arguments);
                                if(m instanceof _promise) {
                                    m.thens = this.thens;
                                    arr.push(m);
                                }
                            }
                        }
                        var l = arr.length;
                        if(l === 0) {
                            continue;
                        } else {
                            for(var i = 0; i < l; i++) {
                                arr[i].promiseArr = arr;
                                arr[i].action = action;
                                arr[i].resolveCount = 0;
                            }
                            break;
                        }
                    } else {
                        if(doneFn instanceof _promise) {
                            doneFn.thens = this.thens;
                            break;
                        } else {
                            n = doneFn.apply(null, arguments);
                            if(n instanceof _promise) {
                                n.thens = this.thens;
                                break;
                            }
                        }
                        continue;
                    }
    
                }
            },
    
            reject: function() {
                if(this.state !== "") return;
                if(this.promiseArr && this.promiseArr[0].action === this._CONSTANT.any) {
                    if(this.promiseArr[this.promiseArr.length - 1] !== this) {
                        return;
                    }
                }
                this.state = this._CONSTANT.rejected;
                if(!this.thens) return;
                if(this.thens[0] && this.thens[0].finallyCB) this.thens[0].finallyCB.apply(null, arguments);
                var t, n;
                while(t = this.thens.shift()) {
                    if(typeof t === this._CONSTANT.number) {
                        var self = this;
                        setTimeout(function() {
                            var prms = new _promise(self.thens);
                            prms.resolve();
                        }, t);
                        break;
                    }
                    if(t.fail) {
                        n = t.fail.apply(null, arguments);
                        if(n instanceof _promise) {
                            n.thens = this.thens;
                            break;
                        }
                        continue;
                    }
                    break;
                }
            },
    
            notify: function() {
                var t = this.thens[0];
                t.progress.apply(null, arguments);
            },
    
            then: function(done, fail, progress) {
                this.thens.push({
                    done: done,
                    fail: fail,
                    progress: progress
                });
                return this;
            },
    
            any: function(done, fail, progress) {
                this.thens.push({
                    done: done,
                    fail: fail,
                    progress: progress,
                    action: this._CONSTANT.any
                });
                return this;
            },
    
            done: function(done) {
                if(this.thens.length === 0 || this.thens[this.thens.length - 1].done) {
                    this.thens.push({
                        done: done
                    });
                } else {
                    this.thens[this.thens.length - 1].done = done;
                }
                return this;
            },
    
            fail: function(fail) {
                if(this.thens.length === 0 || this.thens[this.thens.length - 1].fail) {
                    this.thens.push({
                        fail: fail
                    });
                } else {
                    this.thens[this.thens.length - 1].fail = fail;
                }
                return this;
            },
    
            progress: function(progress) {
                if(this.thens.length === 0 || this.thens[this.thens.length - 1].progress) {
                    this.thens.push({
                        progress: progress
                    });
                } else {
                    this.thens[this.thens.length - 1].progress = progress;
                }
                return this;
            },
    
            ensure: function(finallyCB) {
                if(this.thens.length === 0 || this.thens[this.thens.length - 1].finallyCB) {
    
                    this.thens.push({
                        finallyCB: finallyCB
                    });
                } else {
                    this.thens[this.thens.length - 1].finallyCB = finallyCB;
                }
                return this;
            },
    
            always: function(alwaysCB, progress) {
                this.thens.push({
                    done: alwaysCB,
                    fail: alwaysCB,
                    progress: progress
                });
                return this;
            },
    
            wait: function(ms) {
                this.thens.push(~~ms);
                return this;
            }
        }
    
        var Promise = function(parameter) {
            var prms = new _promise();
            if(parameter) {
                if(arguments.length > 1) {
                    prms.thens[0] = {};
                    prms.thens[0].done = [];
                    prms.thens[0].done.push.apply(prms.thens[0].done, arguments);
                    setTimeout(function() {
                        prms.resolve();
                    }, 1)
                } else {
                    prms = parameter();
                    if(prms instanceof _promise) return prms;
                }
            }
            return prms;
        };
    
        Promise.when = function() {
            var prms = new _promise();
            prms.thens[0] = {};
            prms.thens[0].done = [];
            prms.thens[0].done.push.apply(prms.thens[0].done, arguments);
            setTimeout(function() {
                prms.resolve();
            }, 1)
            return prms;
        };
    
        Promise.any = function() {
            var prms = new _promise();
            prms.thens[0] = {};
            prms.thens[0].action = prms._CONSTANT.any;
            prms.thens[0].done = [];
            prms.thens[0].done.push.apply(prms.thens[0].done, arguments);
            setTimeout(function() {
                prms.resolve();
            }, 1)
            return prms;
        };
    
        Promise.timeout = function(promise, ms) {
            setTimeout(function() {
                promise.reject();
            }, ms);
            return promise;
        }
    
        Promise.gtTime = function(promise, ms) {
            promise.state = promise._CONSTANT.pending;
            setTimeout(function() {
                if(promise.state == promise._CONSTANT.resolved) {
                    promise.state = "";
                    promise.resolve();
                }
                promise.state = "";
            }, ms);
            return promise;
        }
    
        if(typeof module === "object" && module && typeof module.exports === "object") {
            module.exports = Promise;
        } else {
            window.Promise = Promise;
    
            if(typeof define === "function" && define.amd) {
                define("promise", [], function() {
                    return Promise;
                });
            }
        }
    }(window));

    promise.js提供了done和resolve方法,done负责注册成功的回调函数,resolve负责触发。

            function cb() {
                alert('success')
            }
            var prms = Promise()
            prms.done(cb)
            setTimeout(function() {
                prms.resolve()
            }, 3000)

    在3秒之后,浏览器将alert  “success”。

    当然你也可以通过prms.resolve(“xxx”)传递参数给cb函数使用,如:

            function cb(num) {
                alert(num)
            }
            var prms = Promise()
            prms.done(cb)
            setTimeout(function() {
                prms.resolve(1)
            }, 3000)

    在3秒之后,浏览器将alert  “1”。

    fail/reject

    fail函数负责注册失败的回调函数,reject负责触发。如:

            function cb() {
                alert('fail')
            }
            var prms = Promise()
            prms.fail(cb)
            setTimeout(function () {
                prms.reject()
            }, 3000)

    progress/notify

    progress函数负责注册处理中进度的回调函数,notify负责触法。如:

            function cb() {
                alert('progress')
            }
            var prms = Promise()
            prms.progress(cb)
            setInterval(function() {
                prms.notify()
            }, 2000)

    每隔两秒浏览器会弹出一个progress。

    chain

            function cb1() {
                alert('success')
            }
            function cb2() {
                alert('fail')
            }
            function cb3() {
                alert('progress')
            }
            var prms = Promise();
            prms.done(cb1).fail(cb2).progress(cb3)
            setTimeout(function () {
                prms.resolve()
                //prms.reject()
                //prms.notify()
    
            }, 3000)

    then

            function fn1() {
                alert('success')
            }
            function fn2() {
                alert('fail')
            }
            function fn3() {
                alert('progress')
            }
            var prms = Promise()
            prms.then(fn1, fn2, fn3)
            prms.resolve()
            prms.reject()
            prms.notify()

    当然也支持prms.then().then().then()……….

    当then的第一个参数为一个数组的时候,要等所有task都完成:

    f1().then([f2_1, f2_2]).then(f3)

    如上面的代码:

    f1执行完后,同时执行f2_1和f2_2,当f2_1和f2_2全部都执行完成才会执行f3。

    any

    f1().any([f2_1, f2_2]).then(f3)

    f1执行完后,同时执行f2_1和f2_2,当f2_1和f2_2中的任意一个执行完成才会执行f3。

    always

            var prms = Promise()
            prms.always(function () {
                alert(2)
            })
            setTimeout(function () {
                // prms.resolve()
                prms.reject()
            }, 3000)

    always(fn)等同于then(fn,fn),也等同于done(fn).fail(fn)

    wait

            function f10() {
                var promise = Promise();
                setTimeout(function () {
    
                    console.log(10);
                    promise.resolve();
                }, 4500)
    
                return promise;
            }
    
            function f11() {
                var promise = Promise();
                setTimeout(function () {
    
                    console.log(11);
                    promise.resolve();
                }, 1500)
    
                return promise;
            }
    
            f11().wait(5000).then(f10)  //execute f11 then wait 5000ms then execute f10

    ensure

    ensure方法类似try…catch..finally中的finally,不管task成功失败都会执行。

    Promise.when

            Promise.when(f1(), f2()).then(f3).then(f4)
          
            function f1() {
                var promise = Promise();
                setTimeout(function () {
    
                    console.log(1);
                    promise.resolve("from f1");
                }, 1500)
    
                return promise;
            }
    
            function f2() {
                var promise = Promise();
                setTimeout(function () {
    
                    console.log(2);
                    promise.resolve();
                }, 5500)
    
                return promise;
            }
    
            function f3() {
                var promise = Promise();
                setTimeout(function () {
    
                    console.log(3);
                    promise.resolve();
                }, 1500)
    
                return promise;
    
            }
    
            function f4() {
                var promise = Promise();
                setTimeout(function () {
    
                    console.log(4);
                    promise.resolve();
                }, 1500)
    
                return promise;
            }

    上面promise.when的等同简略写法也可以是:Promise(f1(),f2()).then….

    Promise.any

    Promise.any的使用和when一样,when的意义是等所有task都完成再执行后面的task,而any的意义是任何一个task完成就开始执行后面的task。

    Promise.timeout

            Promise.timeout(f1(), 2000).then(f2, function () {
                alert("timeout");
            }).wait(5000).then(f3);
            function f1() {
                var promise = Promise();
                setTimeout(function () {
    
                    console.log(1);
                    promise.resolve("from f1");
                }, 1500)
    
                return promise;
            }
    
            function f2() {
                var promise = Promise();
                setTimeout(function () {
    
                    console.log(2);
                    promise.resolve();
                }, 1500)
    
                return promise;
            }
    
            function f3() {
                var promise = Promise();
                setTimeout(function () {
    
                    console.log(3);
                    promise.resolve();
                }, 1500)
    
                return promise;
    
            }

    with wind.js

        <script src="wind-all-0.7.3.js"></script>
        <script src="promise.js"></script>
        <script>
    
            Wind.Promise.create = function (fn) {
                var prms = Promise();
                fn(prms.resolve, prms.reject);
                return prms;
            }
    
            var testAsync = eval(Wind.compile("promise", function () {
                for (var i = 0; i < 3; i++) {   //loop 3 times
                    var aa = $await(f1());
                    alert(aa);                  //alert “from f1” 
                    $await(f2().wait(3000));    //execute f2 then wait 3000ms
                    $await(f3());
                }
            }));
    
            testAsync();
    
            function f1() {
                var promise = Promise();
                setTimeout(function () {
                    console.log(1);
                    promise.resolve("from f1");
                }, 2500)
    
                return promise;
            }
    
            function f2() {
                var promise = Promise();
                setTimeout(function () {
                    console.log(2);
                    promise.resolve();
                }, 1500)
    
                return promise;
            }
    
            function f3() {
                var promise = Promise();
                setTimeout(function () {
                    console.log(3);
                    promise.resolve();
                }, 1500)
    
                return promise;
            }
        </script>

    That’s all.Have Fun!

     

     

     
     

    用Promise组织程序

    一、Promise基本用法

    很多文章介绍Promise给的例子是这样的:

    new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('POST', location.href, true);
        xhr.send(null);
        xhr.addEventListener('readystatechange', function(e){
           if(xhr.readyState === 4) {
               if(xhr.status === 200) {
                   resolve(xhr.responseText);
               } else {
                   reject(xhr);
               }
           }
        })
    }).then(function(txt){
        console.log();
    })

    一定会有小朋友好奇,说尼玛,这不是比回调还恶心?

    这种写法的确是能跑得起来啦……不过,按照Promise的设计初衷,我们编程需要使用的概念并非"Promise对象",而是promise函数,凡是以Promise作为返回值的函数,称为promise函数(我暂且取了这个名字)。所以应该是这样的:

    function doSth() {
        return new Promise(function(resolve, reject) {
            //做点什么异步的事情
            //结束的时候调用 resolve,比如:
            setTimeout(function(){
                resolve(); //这里才是真的返回
            },1000)
        })
    }

    如果你不喜欢这样的写法,还可以使用defer风格的promise

    function doSth2() {
        var defer = Promise.defer();
        //做点什么异步的事情
        //结束的时候调用 defer.resolve,比如:
        setTimeout(function(){
            defer.resolve(); //这里才是真的返回
        },1000)
    
        return defer.promise;
    }

    总之两种是没什么区别啦。

    然后你就可以这么干:

    doSth().then(doSth2).then(doSth);

    这样看起来就顺眼多了吧。

    其实说简单点,promise最大的意义在于把嵌套的回调变成了链式调用(详见第三节,顺序执行),比如以下

    //回调风格
    loadImage(img1,function(){
        loadImage(img2,function(){
            loadImage(img3,function(){
    
            });
        });
    });
    
    //promise风格
    Promise.resolve().then(function(){
        return loadImage(img1);
    }).then(function(){
        return loadImage(img2);
    }).then(function(){
        return loadImage(img3);
    });

    后者嵌套关系更少,在多数人眼中会更易于维护一些。

    二、Promise风格的API

    在去完cssconf回杭州的火车上,我顺手把一些常见的JS和API写成了promise方式:

    function get(uri){
        return http(uri, 'GET', null);
    }
    
    function post(uri,data){
        if(typeof data === 'object' && !(data instanceof String || (FormData && data instanceof FormData))) {
            var params = [];
            for(var p in data) {
                if(data[p] instanceof Array) {
                    for(var i = 0; i < data[p].length; i++) {
                        params.push(encodeURIComponent(p) + '[]=' + encodeURIComponent(data[p][i]));
                    }
                } else {
                    params.push(encodeURIComponent(p) + '=' + encodeURIComponent(data[p]));
                }
            }
            data = params.join('&');
        }
    
    
        return http(uri, 'POST', data || null, {
            "Content-type":"application/x-www-form-urlencoded"
        });
    }
    
    function http(uri,method,data,headers){
        return new Promise(function(resolve, reject) {
            var xhr = new XMLHttpRequest();
            xhr.open(method,uri,true);
            if(headers) {
                for(var p in headers) {
                    xhr.setRequestHeader(p, headers[p]);
                }
            }
            xhr.addEventListener('readystatechange',function(e){
                if(xhr.readyState === 4) {
                    if(String(xhr.status).match(/^2dd$/)) {
                        resolve(xhr.responseText);
                    } else {
                        reject(xhr);
                    }
                }
            });
            xhr.send(data);
        })
    }
    
    function wait(duration){
        return new Promise(function(resolve, reject) {
            setTimeout(resolve,duration);
        })
    }
    
    function waitFor(element,event,useCapture){
        return new Promise(function(resolve, reject) {
            element.addEventListener(event,function listener(event){
                resolve(event)
                this.removeEventListener(event, listener, useCapture);
            },useCapture)
        })
    }
    
    function loadImage(src) {
        return new Promise(function(resolve, reject) {
            var image = new Image;
            image.addEventListener('load',function listener() {
                resolve(image);
                this.removeEventListener('load', listener, useCapture);
            });
            image.src = src;
            image.addEventListener('error',reject);})}function runScript(src){returnnewPromise(function(resolve, reject){var script = document.createElement('script');
            script.src = src;
            script.addEventListener('load',resolve);
            script.addEventListener('error',reject);(document.getElementsByTagName('head')[0]|| document.body || document.documentElement).appendChild(script);})}function domReady(){returnnewPromise(function(resolve, reject){if(document.readyState ==='complete'){
                resolve();}else{
                document.addEventListener('DOMContentLoaded',resolve);}})}

    看到了吗,Promise风格API跟回调风格的API不同,它的参数跟同步的API是一致的,但是它的返回值是个Promise对象,要想得到真正的结果,需要在then的回调里面拿到。

    值得一提的是,下面这样的写法:

    waitFor(document.documentElement,'click').then(function(){
        console.log('document clicked!')
    })

    这样的事件响应思路上是比较新颖的,不同于事件机制,它的事件处理代码仅会执行一次。

    通过这些函数的组合,我们可以更优雅地组织异步代码,请继续往下看。

    三、使用Promise组织异步代码

    函数调用/并行

    Promise.all跟then的配合,可以视为调用部分参数为Promise提供的函数。譬如,我们现在有一个接受三个参数的函数

    function print(a, b, c) {
        console.log(a + b + c);
    }

    现在我们调用print函数,其中a和b是需要异步获取的:

    var c = 10;
    
    print(geta(), getb(), 10); //这是同步的写法
    
    Promise.all([geta(), getb(), 10]).then(print); //这是 primise 的异步写法

    竞争

    如果说Primise.all是promise对象之间的“与”关系,那么Promise.race就是promise对象之间的“或”关系。

    比如,我要实现“点击按钮或者5秒钟之后执行”

    var btn = document.getElementsByTagName('button');
    
    Promise.race(wait(5000), waitFor(btn, click)).then(function(){
        console.log('run!')
    })

    异常处理

    异常处理一直是回调的难题,而promise提供了非常方便的catch方法:

    在一次promise调用中,任何的环节发生reject,都可以在最终的catch中捕获到

    Promise.resolve().then(function(){
        return loadImage(img1);
    }).then(function(){
        return loadImage(img2);
    }).then(function(){
        return loadImage(img3);
    }).catch(function(err){
        //错误处理
    })

    这非常类似于JS的try catch功能。

    复杂流程

    接下来,我们来看比较复杂的情况。

    promise有一种非常重要的特性:then的参数,理论上应该是一个promise函数,而如果你传递的是普通函数,那么默认会把它当做已经resolve了的promise函数。

    这样的特性让我们非常容易把promise风格的函数跟已有代码结合起来。

    为了方便传参数,我们编写一个currying函数,这是函数式编程里面的基本特性,在这里跟promise非常搭,所以就实现一下:

    function currying(){
        var f = arguments[0];
        var args = Array.prototype.slice.call(arguments,1);
        return function(){
            args.push.apply(args,arguments);
            return f.apply(this,args);
        }
    }

    currying会给某个函数"固化"几个参数,并且返回接受剩余参数的函数。比如之前的函数,可以这么玩:

    var print2 = currying(print,11);
    
    print2(2, 3); //得到 11 + 2 + 3 的结果,16
    
    var wait1s = currying(wait,1000);
    
    wait1s().then(function(){
        console.log('after 1s!');
    })

    有了currying,我们就可以愉快地来玩链式调用了,比如以下代码:

    Promise.race([
        domReady().then(currying(wait,5000)), 
        waitFor(btn, click)])
        .then(currying(runScript,'a.js'))
        .then(function(){
            console.log('loaded');
            return Promise.resolve();
        });

    表示“domReady发生5秒或者点击按钮后,加载脚本并执行,完毕后打印loaded”。

    四、总结

    promise作为一个新的API,是几乎完全可polyfill的,这在JS发展中这是很少见的情况,它的API本身没有什么特别的功能,但是它背后代表的编程思路是很有价值的。

     
     

      

     
     
     
     
     

    一.deferred对象简介

    deferred对象是jquery回调函数的解决方案,解决了如何处理耗时操作的问题,对耗时操作提供了更好的控制,以及统一的编程接口。

    二.语法

    1.与ajax进行写法比较:

    复制代码
     1      //ajax传统写法
     2      //$.ajax()接受一个对象参数,这个对象包含两个方法:
     3      //success方法指定操作成功后的回调函数,error方法指定操作失败后的回调函数。
     4     $.ajax({
     5     url: "test.html",
     6     success: function(){
     7       alert("哈哈,成功了!");
     8     },
     9     error:function(){
    10       alert("出错啦!");
    11     }
    12   });
    13 
    14       //deferred对象写法
    15       $.ajax("test.html")
    16        .done(function(){alert("哈哈,成功了!");})
    17        .fail(function(){alert("出错啦!");});
    18        //可以看到,done()相当于success方法,fail()相当于error方法。采用链式写法以后,代码的可读性大大提高。
    复制代码

    2.指定同一操作的多个回调函数

    1 //deferred对象的一大好处,就是它允许你自由添加多个回调函数。
    2 //回调函数可以添加任意多个,它们按照添加顺序执行。
    3     $.ajax("test.html")
    4   .done(function(){ alert("哈哈,成功了!");} )
    5   .fail(function(){ alert("出错啦!"); } )
    6   .done(function(){ alert("第二个回调函数!");} );    

    3.为多个操作指定回调函数

    复制代码
    1 //deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数
    2     $.when($.ajax("test1.html"), $.ajax("test2.html"))
    3   .done(function(){ alert("哈哈,成功了!"); })
    4   .fail(function(){ alert("出错啦!"); });
    5 //这段代码的意思是,先执行两个操作$.ajax("test1.html")和$.ajax("test2.html")
    6 //如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。
    7 //when()参数的关系类似于逻辑运算“与”
    复制代码

    4.deferred.resolve()方法和deferred.reject()方法

    jQuery规定,deferred对象有三种执行状态----未完成,已完成和已失败。如果执行状态是"已完成"(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是"已失败",调用fail()方法指定的回调函数;如果执行状态是"未完成",则继续等待,或者调用progress()方法(progress():当Deferred(延迟)对象生成进度通知时,调用添加处理程序。参数可以是一个单一的函数或函数数组。)指定的回调函数。

    deferred.resolve()方法和deferred.reject()方法就是为了改变deferred对象的状态,从而决定执行哪个回调函数。

    复制代码
     1     var dtd = $.Deferred(); // 新建一个Deferred对象
     2   var wait = function(dtd){
     3    var tasks = function(){
     4       alert("执行完毕!");
     5       dtd.reject(); // 改变Deferred对象的执行状态
     6     };
     7     setTimeout(tasks,5000);
     8     return dtd;
     9   };
    10   $.when(wait(dtd))
    11   .done(function(){ alert("哈哈,成功了!"); })
    12   .fail(function(){ alert("出错啦!"); });
    复制代码

    5.deferred.promise()方法

    第4点代码中的dtd对象是全局对象,所以它的执行状态可以从外部改变。

    复制代码
     1       var dtd = $.Deferred(); // 新建一个Deferred对象
     2   var wait = function(dtd){
     3     var tasks = function(){
     4       alert("执行完毕!");
     5       dtd.resolve(); // 改变Deferred对象的执行状态
     6     };
     7     setTimeout(tasks,5000);
     8     return dtd;
     9   };
    10   $.when(wait(dtd))
    11   .done(function(){ alert("哈哈,成功了!"); })
    12   .fail(function(){ alert("出错啦!"); });
    13   dtd.resolve();
    14 //在代码的尾部加了一行dtd.resolve(),这就改变了dtd对象的执行状态,因此导致done()方法立刻执行,跳出"哈哈,成功了!"的提示框,等5秒之后再跳出"执行完毕!"的提示框
    复制代码

    为了避免这种情况,jQuery提供了deferred.promise()方法。它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。

    复制代码
     1    var dtd = $.Deferred(); // 新建一个Deferred对象
     2   var wait = function(dtd){
     3   var tasks = function(){
     4       alert("执行完毕!");
     5       dtd.resolve(); // 改变Deferred对象的执行状态
     6   };
     7 
     8     setTimeout(tasks,5000);
     9     return dtd.promise(); // 返回promise对象
    10   };
    11   var d = wait(dtd); // 新建一个d对象,改为对这个对象进行操作
    12   $.when(d)
    13   .done(function(){ alert("哈哈,成功了!"); })
    14   .fail(function(){ alert("出错啦!"); });
    15   d.resolve(); // 此时,这个语句是无效的
    16 //在上面的这段代码中,wait()函数返回的是promise对象。然后,我们把回调函数绑定在这个对象上面,而不是原来的deferred对象上面。这样的好处是,无法改变这个对象的执行状态,要想改变执行状态,只能操作原来的deferred对象。
    17 
    18 //或者可以将dtd对象变成wait()函数的内部对象:
    19        var wait = function(dtd){
    20     var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象
    21     var tasks = function(){
    22       alert("执行完毕!");
    23       dtd.resolve(); // 改变Deferred对象的执行状态
    24     };
    25 
    26     setTimeout(tasks,5000);
    27     return dtd.promise(); // 返回promise对象
    28   };
    29   $.when(wait())
    30   .done(function(){ alert("哈哈,成功了!"); })
    31   .fail(function(){ alert("出错啦!"); });
    复制代码

    6.普通操作的回调函数接口

    (1)deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作。也就是说,任何一个操作----不管是ajax操作还是本地操作,也不管是异步操作还是同步操作----都可以使用deferred对象的各种方法,指定回调函数。

     使用$.when():参数只能是deferred对象。

    假定有一个很耗时的操作wait,为它指定一个回调函数。

    复制代码
     1    var dtd = $.Deferred(); // 新建一个deferred对象
     2   var wait = function(dtd){
     3     var tasks = function(){
     4       alert("执行完毕!");
     5       dtd.resolve(); // 改变deferred对象的执行状态
     6     };
     7     setTimeout(tasks,5000);
     8     return dtd;
     9   };
    10 //wait()函数返回的是deferred对象,可以加上链式操作。
    11   $.when(wait(dtd))
    12   .done(function(){ alert("哈哈,成功了!"); })
    13   .fail(function(){ alert("出错啦!"); });
    14 //wait()函数运行完,就会自动运行done()方法指定的回调函数。
    复制代码

    (2)防止执行状态被外部改变的方法:使用deferred对象的建构函数$.Deferred(),

    wait函数还是保持不变,我们直接把它传入$.Deferred():

    $.Deferred()可以接受一个函数名作为参数,$.Deferred()所生成的deferred对象将作为这个函数的默认参数。

    1     $.Deferred(wait)
    2   .done(function(){ alert("哈哈,成功了!"); })
    3   .fail(function(){ alert("出错啦!"); });

    (3)可以直接在wait对象上部署deferred接口:

    复制代码
     1    var dtd = $.Deferred(); // 生成Deferred对象
     2   var wait = function(dtd){
     3    var tasks = function(){
     4       alert("执行完毕!");
     5       dtd.resolve(); // 改变Deferred对象的执行状态
     6     };
     7     setTimeout(tasks,5000);
     8   };
     9   dtd.promise(wait);//在wait对象上部署Deferred接口。正是因为有了这一行,后面才能直接在wait上面调用done()和fail()。
    10   wait.done(function(){ alert("哈哈,成功了!"); })
    11   .fail(function(){ alert("出错啦!"); });
    12   wait(dtd);
    复制代码

    7.总结

          (1) $.Deferred() 生成一个deferred对象。

      (2) deferred.done() 指定操作成功时的回调函数

      (3) deferred.fail() 指定操作失败时的回调函数

      (4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。

      (5) deferred.resolve() 手动改变deferred对象的运行状态为"已完成",从而立即触发done()方法。

      (6)deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()方法。

      (7) $.when() 为多个操作指定回调函数。

      (8)deferred.then():有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。

    1     $.when($.ajax( "/main.php" ))
    2   .then(successFunc, failureFunc );

    如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。

      (9)deferred.always():这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。

    1     $.ajax( "test.html" )
    2   .always( function() { alert("已执行!");} );

    文档参考:

     
     
  • 相关阅读:
    moya
    语言是人脑中影像的表述工具
    Ad-hoc--拉丁文--for this purpose only
    语言是人类对自我意识的表述工具:对世界和事物多深刻的认识,就会有多丰富和符合 逻辑的语言表述
    知行合一:知其然知其所以然
    语言是世界观、方法论的重组和表述
    任何能用编程语言描述的东西都能够用人类语言描述
    面向对象的几个视角
    待解决问题 jscore 与 node.js jsbridge
    浅谈CSRF攻击方式
  • 原文地址:https://www.cnblogs.com/libin-1/p/5947602.html
Copyright © 2011-2022 走看看