第一部分,jQuery-1.5 之后的 ajax
本地址 http://www.cnblogs.com/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
對象會有done
fail
和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.resolve
dtd.reject
dtd.then
dtd.done
dtd.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源碼解讀》