Home > Web Front-end > JS Tutorial > body text

Thoroughly understand Promises in Javascript

高洛峰
Release: 2016-10-11 09:38:15
Original
1470 people have browsed it

ES6原生提供了 Promise 对象。

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

Thoroughly understand Promises in Javascript

所谓 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(&#39;执行任务1&#39;);
                            resolve(&#39;执行任务1成功&#39;);
                        }, 2000);
                    });
                }

                function pms2() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log(&#39;执行任务2&#39;);
                            resolve(&#39;执行任务2成功&#39;);
                        }, 2000);
                    });
                }

                function pms3() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log(&#39;执行任务3&#39;);
                            resolve(&#39;执行任务3成功&#39;);
                        }, 2000);
                    });
                }
                pms1().then(function(data) {
                        console.log(&#39;第1个回调:&#39; + data);
                        return pms2();
                    })
                    .then(function(data) {
                        console.log(&#39;第2个回调:&#39; + data);
                        return pms3();
                    })
                    .then(function(data) {
                        console.log(&#39;第3个回调:&#39; + data);
                        return &#39;还没完!该结束了吧!&#39;
                    }).then(function(data) {
                        console.log(data);
                    });
            }
        </script>
    </head>

    <body>

    </body>

</html>
Copy after login

Thoroughly understand Promises in Javascript

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

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(&#39;数字太大了吧!&#39;);
                            }
                        }, 2000);
                    });
                }
                setInterval(function() {
                    pms1().then(function(data) {    //小于等于5的
                        console.log(data);
                    }, function(data) {     //大于的
                        console.log(data);
                    })
                }, 1000);
            }
        </script>
    </head>

    <body>
    </body>

</html>
Copy after login

Thoroughly understand Promises in Javascript

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(&#39;执行任务1&#39;);
                            resolve(&#39;执行任务1成功&#39;);
                        }, 2000);
                    });
                }

                function pms2() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log(&#39;执行任务2&#39;);
                            resolve(&#39;执行任务2成功&#39;);
                        }, 2000);
                    });
                }

                function pms3() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log(&#39;执行任务3&#39;);
                            resolve(&#39;执行任务3成功&#39;);
                        }, 2000);
                    });
                }
                Promise.all([pms1(), pms2(), pms3()]).then(function(data) {
                    console.log(data);
                    console.log({}.toString.call(data));
                })
            }
        </script>
    </head>

    <body>

    </body>

</html>
Copy after login

Thoroughly understand Promises in Javascript

用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(&#39;执行任务1&#39;);
                            resolve(&#39;执行任务1成功&#39;);
                        }, 1000);
                    });
                }

                function pms2() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log(&#39;执行任务2&#39;);
                            resolve(&#39;执行任务2成功&#39;);
                        }, 2000);
                    });
                }

                function pms3() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log(&#39;执行任务3&#39;);
                            resolve(&#39;执行任务3成功&#39;);
                        }, 3000);
                    });
                }
                Promise.race([pms1(), pms2(), pms3()]).then(function(data) {
                    console.log(data);   //注意上面的延时时间
                })
            }
        </script>
    </head>

    <body>

    </body>

</html>
Copy after login

Thoroughly understand Promises in Javascript

看到没:只有第一个执行了回调!

在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。

这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作。

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

jquery用$.Deferred实现了Promise规范,$.Deferred是个什么玩意呢?还是老方法,打印出来看看,先有个直观印象:

var def = $.Deferred();
console.log(def);
Copy after login

输出如下:

Thoroughly understand Promises in Javascript

$.Deferred()返回一个对象,我们可以称之为Deferred对象,上面挂着一些熟悉的方法如:done、fail、then等。jquery就是用这个Deferred对象来注册异步操作的回调函数,修改并传递异步操作的状态。

Deferred对象的基本用法如下,为了不与ajax混淆,我们依旧举setTimeout的例子:

<!doctype html>
<html>

    <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(&#39;执行完成&#39;);
                        def.resolve(&#39;随便什么数据&#39;);
                    }, 2000);
                    return def;
                }
                runAsync().then(function(data) {
                    console.log(data)
                });
            })
        </script>
    </head>

    <body>

    </body>

</html>
Copy after login


Thoroughly understand Promises in Javascript

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

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

区别在何处一看便知。由于jquery的def对象本身就有resolve方法,所以我们在创建def对象的时候并未像ES6这样传入了一个函数参数,是空的。在后面可以直接def.resolve()这样调用。

<!doctype html>
<html>

    <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(&#39;执行完成&#39;);
                        def.resolve(&#39;随便什么数据&#39;);
                    }, 2000);
                    return def;
                }
                var pms=runAsync();
                pms.then(function(data) {
                    console.log(data)
                });
                pms.resolve(&#39;我穿越了!&#39;)
            })
        </script>
    </head>

    <body>

    </body>

</html>
Copy after login


Thoroughly understand Promises in Javascript

这样也有一个弊端,因为执行runAsync()可以拿到def对象,而def对象上又有resolve方法,那么岂不是可以在外部就修改def的状态了?比如我把上面的代码修改如下:

现象会如何呢?并不会在2秒后输出“执行完成”,而是直接输出“我穿越了”。因为我们在异步操作执行完成之前,没等他自己resolve,就在外部给resolve了。这显然是有风险的,比如你定义的一个异步操作并指定好回调函数,有可能被别人给提前结束掉,你的回调函数也就不能执行了。

怎么办?jquery提供了一个promise方法,就在def对象上,他可以返回一个受限的Deferred对象,所谓受限就是没有resolve、reject等方法,无法从外部来改变他的状态,用法如下:

<!doctype html>
<html>

    <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(&#39;执行完成&#39;);
                        def.resolve(&#39;随便什么数据&#39;);
                    }, 2000);
                    return def.promise();
                }
                var pms=runAsync();
                pms.then(function(data) {
                    console.log(data)
                });
                //pms.resolve(&#39;我穿越了!&#39;);    //这一句会报错jquery-3.1.1.min.js:2 Uncaught TypeError: pms.resolve is not a function
            })
        </script>
    </head>

    <body>

    </body>

</html>
Copy after login

Thoroughly understand Promises in Javascript

then的链式调用
既然Deferred也是Promise规范的实现者,那么其他特性也必须是支持的。链式调用的用法如下:
Copy after login
<!doctype html>
<html>

    <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(&#39;执行完成&#39;);
                        def.resolve(&#39;随便什么数据&#39;);
                    }, 2000);
                    return def.promise();
                }
                var pms = runAsync();

                pms.then(function(data) {
                        console.log(&#39;1:&#39; + data);
                        return runAsync();
                    })
                    .then(function(data) {
                        console.log(&#39;2:&#39; + data);
                        return runAsync();
                    })
                    .then(function(data) {
                        console.log(&#39;3:&#39; + data);
                    });
                //pms.resolve(&#39;我穿越了!&#39;);    //这一句会报错jquery-3.1.1.min.js:2 Uncaught TypeError: pms.resolve is not a function
            })
        </script>
    </head>

    <body>

    </body>

</html>
Copy after login

Thoroughly understand Promises in Javascript

done与fail

我们知道,Promise规范中,then方法接受两个参数,分别是执行完成和执行失败的回调,而jquery中进行了增强,还可以接受第三个参数,就是在pending状态时的回调,如下:

deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
Copy after login

除此之外,jquery还增加了两个语法糖方法,done和fail,分别用来指定执行完成和执行失败的回调,也就是说这段代码:

d.then(function(){
    console.log(&#39;执行完成&#39;);
}, function(){
    console.log(&#39;执行失败&#39;);
});
Copy after login

与这段代码是等价的:

d.done(function(){
    console.log(&#39;执行完成&#39;);
})
.fail(function(){
    console.log(&#39;执行失败&#39;);
});
Copy after login

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(&#39;全部执行完成&#39;);
    console.log(data1, data2, data3);
});
Copy after login

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(&#39;请求发送完毕&#39;);
});
Copy after login

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

success、error与complete

这三个方法或许是我们用的最多的,使用起来是这样的:

$.ajax(/*...*/)
.success(function(){/*...*/})
.error(function(){/*...*/})
.complete(function(){/*...*/})
Copy after login

分别表示ajax请求成功、失败、结束的回调。这三个方法与Deferred又是什么关系呢?其实就是语法糖,success对应done,error对应fail,complete对应always,就这样,只是为了与ajax的参数名字上保持一致而已,更方便大家记忆,看一眼源码:

deferred.promise( jqXHR ).complete = completeDeferred.add;
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;
Copy after login

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  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 
Copy after login
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!