首頁 web前端 js教程 徹底理解Javascript 中的 Promise

徹底理解Javascript 中的 Promise

Oct 11, 2016 am 09:38 AM

ES6原生提供了 Promise 对象。

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

徹底理解Javascript 中的 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(&#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>
登入後複製

徹底理解Javascript 中的 Promise

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

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>
登入後複製

徹底理解Javascript 中的 Promise

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>
登入後複製

徹底理解Javascript 中的 Promise

用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>
登入後複製

徹底理解Javascript 中的 Promise

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

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

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

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

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

var def = $.Deferred();
console.log(def);
登入後複製

输出如下:

徹底理解Javascript 中的 Promise

$.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>
登入後複製


徹底理解Javascript 中的 Promise

在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>
登入後複製


徹底理解Javascript 中的 Promise

这样也有一个弊端,因为执行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>
登入後複製

徹底理解Javascript 中的 Promise

then的链式调用
既然Deferred也是Promise规范的实现者,那么其他特性也必须是支持的。链式调用的用法如下:
登入後複製
<!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>
登入後複製

徹底理解Javascript 中的 Promise

done与fail

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

deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
登入後複製

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

d.then(function(){
    console.log(&#39;执行完成&#39;);
}, function(){
    console.log(&#39;执行失败&#39;);
});
登入後複製

与这段代码是等价的:

d.done(function(){
    console.log(&#39;执行完成&#39;);
})
.fail(function(){
    console.log(&#39;执行失败&#39;);
});
登入後複製

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);
});
登入後複製

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;);
});
登入後複製

明白了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 函式庫 v2.0.0
 */
;
(函數(視窗){
    var_promise=函數(然後){
        this.thens = 那麼|| [];
        this.state=「」;

        這個._CONSTANT = {
            任何:「任何」,
            號碼:“號碼”,
            已解決:“已解決”,
            被拒絕:“被拒絕”,
            待處理:「待處理」
        };
    };

    _promise.prototype = {
        解決:函數(){
            if(this.state==this._CONSTANT.pending){
                this.state = this._CONSTANT.resolved;
                則返回;
            }
            if(this.state !=="") return;
            if(this.promiseArr) {
                for(var i = 0, j = this.promiseArr.length; i  1) {{
                        則回復;
                    }
                }
            }            this.state = this._CONSTANT.resolved;
            if(!this.thens) 返回;
            if(this.thens[0] && this.thens[0].finallyCB) this.thens[0].finallyCB.apply(null, 參數);
            var t,n;
            while(t = this.thens.shift()) {
                if(typeof t === this._CONSTANT.number) {
                    var self = this;
                    setTimeout(函數(){
                        var prms = new _promise(self.thens);
                        prms.resolve();
                    }, t);
                    休息;
                }
                var didFn = t.done,
                    動作 = t.action;
                if(!doneFn) 繼續;
                if(doneFn 備份實例) {
                    var arr = [];
                    for(var i = 0, j = doneFn.length; i 
登入後複製
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何創建和發布自己的JavaScript庫? 如何創建和發布自己的JavaScript庫? Mar 18, 2025 pm 03:12 PM

文章討論了創建,發布和維護JavaScript庫,專注於計劃,開發,測試,文檔和促銷策略。

如何在瀏覽器中優化JavaScript代碼以進行性能? 如何在瀏覽器中優化JavaScript代碼以進行性能? Mar 18, 2025 pm 03:14 PM

本文討論了在瀏覽器中優化JavaScript性能的策略,重點是減少執行時間並最大程度地減少對頁面負載速度的影響。

前端熱敏紙小票打印遇到亂碼問題怎麼辦? 前端熱敏紙小票打印遇到亂碼問題怎麼辦? Apr 04, 2025 pm 02:42 PM

前端熱敏紙小票打印的常見問題與解決方案在前端開發中,小票打印是一個常見的需求。然而,很多開發者在實...

如何使用瀏覽器開發人員工具有效調試JavaScript代碼? 如何使用瀏覽器開發人員工具有效調試JavaScript代碼? Mar 18, 2025 pm 03:16 PM

本文討論了使用瀏覽器開發人員工具的有效JavaScript調試,專注於設置斷點,使用控制台和分析性能。

誰得到更多的Python或JavaScript? 誰得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript開發者的薪資沒有絕對的高低,具體取決於技能和行業需求。 1.Python在數據科學和機器學習領域可能薪資更高。 2.JavaScript在前端和全棧開發中需求大,薪資也可觀。 3.影響因素包括經驗、地理位置、公司規模和特定技能。

如何使用源地圖調試縮小JavaScript代碼? 如何使用源地圖調試縮小JavaScript代碼? Mar 18, 2025 pm 03:17 PM

本文說明瞭如何使用源地圖通過將其映射回原始代碼來調試JAVASCRIPT。它討論了啟用源地圖,設置斷點以及使用Chrome DevTools和WebPack之類的工具。

如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? 如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? Apr 04, 2025 pm 05:09 PM

如何在JavaScript中將具有相同ID的數組元素合併到一個對像中?在處理數據時,我們常常會遇到需要將具有相同ID�...

console.log輸出結果差異:兩次調用為何不同? console.log輸出結果差異:兩次調用為何不同? Apr 04, 2025 pm 05:12 PM

深入探討console.log輸出差異的根源本文將分析一段代碼中console.log函數輸出結果的差異,並解釋其背後的原因。 �...

See all articles