彻底理解 Javascript 中的 Promise

4,414 阅读3分钟
原文链接: www.cnblogs.com

complete那一行那么写,是为了减少重复代码,其实就是把done和fail又调用一次,与always中的代码一样。deferred.promise( jqXHR )这句也能看出,ajax返回的是受限的Deferred对象。

jquery加了这么些个语法糖,虽然上手门槛更低了,但是却造成了一定程度的混淆。一些人虽然这么写了很久,却一直不知道其中的原理,在面试的时候只能答出一些皮毛,这是很不好的。这也是我写这篇文章的缘由。

/*!
 * 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

    
    
    

That’s all.Have Fun!