深入理解 JavaScript 異步系列(2)—— jquery的解決方案
第一部分,jQuery-1.5 之后的 ajax
本地址 http://www.rzrgm.cn/wangfupeng1988/p/6515779.html 未經允許不得轉載~
$.ajax這個函數各位應該都比較熟悉了,要完整的講解 js 的異步操作,就必須先從$.ajax這個方法說起。
想要學到全面的知識,大家就不要著急,跟隨我的節奏來,并且相信我。我安排的內容,肯定都是有用的,對主題無用的東西,我不會拿來占用大家的時間。
本節內容概述
- 傳統的
$.ajax - 1.5 版本之后的
$.ajax - 改進之后的好處
- 和后來的
Promise的關系 - 如何實現的?
傳統的$.ajax
先來一段最常見的$.ajax的代碼,當然是使用萬惡的callback方式
var ajax = $.ajax({ url: 'data.json', success: function () { console.log('success') }, error: function () { console.log('error') } }) console.log(ajax) // 返回一個 XHR 對象
至于這么做會產生什么樣子的詬病,我想大家應該都很明白了。不明白的自己私下去查,但是你也可以繼續往下看,你只需要記住這樣做很不好就是了,要不然 jquery 也不會再后面進行改進
1.5 版本之后的$.ajax
但是從v1.5開始,以上代碼就可以這樣寫了:可以鏈式的執行done或者fail方法
var ajax = $.ajax('data.json') ajax.done(function () { console.log('success 1') }) .fail(function () { console.log('error') }) .done(function () { console.log('success 2') }) console.log(ajax) // 返回一個 deferred 對象
大家注意看以上兩段代碼中都有一個console.log(ajax),但是返回值是完全不一樣的。
v1.5之前,返回的是一個XHR對象,這個對象不可能有done或者fail的方法的v1.5開始,返回一個deferred對象,這個對象就帶有done和fail的方法,并且是等著請求返回之后再去調用
改進之后的好處
這是一個標志性的改造,不管這個概念是誰最先提出的,它在 jquery 中首先大量使用并讓全球開發者都知道原來 ajax 請求還可以這樣寫。這為以后的Promise標準制定提供了很大意義的參考,你可以以為這就是后面Promise的原型。
記住一句話————雖然 JS 是異步執行的語言,但是人的思維是同步的————因此,開發者總是在尋求如何使用邏輯上看似同步的代碼來完成 JS 的異步請求。而 jquery 的這一次更新,讓開發者在一定程度上得到了這樣的好處。
之前無論是什么操作,我都需要一股腦寫到callback中,現在不用了。現在成功了就寫到done中,失敗了就寫到fail中,如果成功了有多個步驟的操作,那我就寫很多個done,然后鏈式連接起來就 OK 了。
和后來的Promise的關系
以上的這段代碼,我們還可以這樣寫。即不用done和fail函數,而是用then函數。then函數的第一個參數是成功之后執行的函數(即之前的done),第二個參數是失敗之后執行的函數(即之前的fail)。而且then函數還可以鏈式連接。
var ajax = $.ajax('data.json') ajax.then(function () { console.log('success 1') }, function () { console.log('error 1') }) .then(function () { console.log('success 2') }, function () { console.log('error 2') })
如果你對現在 ES6 的Promise有了解,應該能看出其中的相似之處。不了解也沒關系,你只需要知道它已經和Promise比較接近了。后面馬上會去講Promise
如何實現的?
明眼人都知道,jquery 不可能改變異步操作需要callback的本質,它只不過是自己定義了一些特殊的 API,并對異步操作的callback進行了封裝而已。
那么 jquery 是如何實現這一步的呢?請聽下回分解!
第二部分,jQuery deferred
上一節講到 jquery v1.5 版本開始,$.ajax可以使用類似當前Promise的then函數以及鏈式操作。那么它到底是如何實現的呢?在此之前所用到的callback在這其中又起到了什么作用?本節給出答案
本節使用的代碼參見這里
本節內容概述
- 寫一個傳統的異步操作
- 使用
$.Deferred封裝 - 應用
then方法 - 有什么問題?
寫一個傳統的異步操作
給出一段非常簡單的異步操作代碼,使用setTimeout函數。
var wait = function () { var task = function () { console.log('執行完成') } setTimeout(task, 2000) } wait()
以上這些代碼執行的結果大家應該都比較明確了,即 2s 之后打印出執行完成。但是我如果再加一個需求 ———— 要在執行完成之后進行某些特別復雜的操作,代碼可能會很多,而且分好幾個步驟 ———— 那該怎么辦? 大家思考一下!
如果你不看下面的內容,而且目前還沒有Promise的這個思維,那估計你會說:直接在task函數中寫就是了!不過相信你看完下面的內容之后,會放棄你現在的想法。
使用$.Deferred封裝
好,接下來我們讓剛才簡單的幾行代碼變得更加復雜。為何要變得更加復雜?是因為讓以后更加復雜的地方變得簡單。這里我們使用了 jquery 的$.Deferred,至于這個是個什么鬼,大家先不用關心,只需要知道$.Deferred()會返回一個deferred對象,先看代碼,deferred對象的作用我們會面會說。
function waitHandle() { var dtd = $.Deferred() // 創建一個 deferred 對象 var wait = function (dtd) { // 要求傳入一個 deferred 對象 var task = function () { console.log('執行完成') dtd.resolve() // 表示異步任務已經完成 } setTimeout(task, 2000) return dtd // 要求返回 deferred 對象 } // 注意,這里一定要有返回值 return wait(dtd) }
以上代碼中,又使用一個waitHandle方法對wait方法進行再次的封裝。waitHandle內部代碼,我們分步驟來分析。跟著我的節奏慢慢來,保證你不會亂。
- 使用
var dtd = $.Deferred()創建deferred對象。通過上一節我們知道,一個deferred對象會有donefail和then方法(不明白的去看上一節) - 重新定義
wait函數,但是:第一,要傳入一個deferred對象(dtd參數);第二,當task函數(即callback)執行完成之后,要執行dtd.resolve()告訴傳入的deferred對象,革命已經成功。第三;將這個deferred對象返回。 - 返回
wait(dtd)的執行結果。因為wait函數中返回的是一個deferred對象(dtd參數),因此wait(dtd)返回的就是dtd————如果你感覺這里很亂,沒關系,慢慢捋,一行一行看,相信兩三分鐘就能捋順!
最后總結一下,waitHandle函數最終return wait(dtd)即最終返回dtd(一個deferred)對象。針對一個deferred對象,它有done fail和then方法(上一節說過),它還有resolve()方法(其實和resolve相對的還有一個reject方法,后面會提到)
應用then方法
接著上面的代碼繼續寫
var w = waitHandle() w.then(function () { console.log('ok 1') }, function () { console.log('err 1') }).then(function () { console.log('ok 2') }, function () { console.log('err 2') })
上面已經說過,waitHandle函數最終返回一個deferred對象,而deferred對象具有done fail then方法,現在我們正在使用的是then方法。至于then方法的作用,我們上一節已經講過了,不明白的同學抓緊回去補課。
執行這段代碼,我們打印出來以下結果。可以將結果對標以下代碼時哪一行。
執行完成
ok 1
ok 2
此時,你再回頭想想我剛才說提出的需求(要在執行完成之后進行某些特別復雜的操作,代碼可能會很多,而且分好幾個步驟),是不是有更好的解決方案了?
有同學肯定發現了,代碼中console.log('err 1')和console.log('err 2')什么時候會執行呢 ———— 你自己把waitHandle函數中的dtd.resolve()改成dtd.reject()試一下就知道了。
dtd.resolve()表示革命已經成功,會觸發then中第一個參數(函數)的執行,dtd.reject()表示革命失敗了,會觸發then中第二個參數(函數)執行
有什么問題?
總結一下一個deferred對象具有的函數屬性,并分為兩組:
dtd.resolvedtd.rejectdtd.thendtd.donedtd.fail
我為何要分成兩組 ———— 這兩組函數,從設計到執行之后的效果是完全不一樣的。第一組是主動觸發用來改變狀態(成功或者失敗),第二組是狀態變化之后才會觸發的監聽函數。
既然是完全不同的兩組函數,就應該徹底的分開,否則很容易出現問題。例如,你在剛才執行代碼的最后加上這么一行試試。
w.reject()
那么如何解決這一個問題?請聽下回分解!
第三部分,jQuery promise
上一節通過一些代碼演示,知道了 jquery 的deferred對象是解決了異步中callback函數的問題,但是
本節使用的代碼參見這里
本節內容概述
- 返回
promise - 返回
promise的好處 - promise 的概念
返回promise
我們對上一節的的代碼做一點小小的改動,只改動了一行,下面注釋。
function waitHandle() { var dtd = $.Deferred() var wait = function (dtd) { var task = function () { console.log('執行完成') dtd.resolve() } setTimeout(task, 2000) return dtd.promise() // 注意,這里返回的是 primise 而不是直接返回 deferred 對象 } return wait(dtd) } var w = waitHandle() // 經過上面的改動,w 接收的就是一個 promise 對象 $.when(w) .then(function () { console.log('ok 1') }) .then(function () { console.log('ok 2') })
改動的一行在這里return dtd.promise(),之前是return dtd。dtd是一個deferred對象,而dtd.promise就是一個promise對象。
promise對象和deferred對象最重要的區別,記住了————promise對象相比于deferred對象,缺少了.resolve和.reject這倆函數屬性。這么一來,可就完全不一樣了。
上一節我們提到一個問題,就是在程序的最后一行加一句w.reject()會導致亂套,你現在再在最后一行加w.reject()試試 ———— 保證亂套不了 ———— 而是你的程序不能執行,直接報錯。因為,w是promise對象,不具備.reject屬性。
返回promise的好處
上一節提到deferred對象有兩組屬性函數,而且提到應該把這兩組徹底分開。現在通過上面一行代碼的改動,就分開了。
waitHandle函數內部,使用dtd.resolve()來該表狀態,做主動的修改操作waitHandle最終返回promise對象,只能去被動監聽變化(then函數),而不能去主動修改操作
一個“主動”一個“被動”,完全分開了。
promise 的概念
jquery v1.5 版本發布時間距離現在(2017年初春)已經老早之前了,那會兒大家網頁標配都是 jquery 。無論里面的deferred和promise這個概念和想法最早是哪位提出來的,但是最早展示給全世界開發者的是 jquery ,這算是Promise這一概念最先的提出者。
其實本次課程主要是給大家分析 ES6 的Promise Generator和async-await,但是為何要從 jquery 開始(大家現在用 jquery 越來越少)?就是要給大家展示一下這段歷史的一些起點和發展的知識。有了這些基礎,你再去接受最新的概念會非常容易,因為所有的東西都是從最初順其自然發展進化而來的,我們要去用一個發展進化的眼光學習知識,而不是死記硬背。
求打賞
如果你看完了,感覺還不錯,歡迎給我打賞 ———— 以激勵我更多輸出優質內容

最后,github地址是 https://github.com/wangfupeng1988/js-async-tutorial 歡迎 star 和 pr
-------
學習作者教程:《前端JS高級面試》《前端JS基礎面試題》《React.js模擬大眾點評webapp》《zepto設計與源碼分析》《json2.js源碼解讀》

浙公網安備 33010602011771號