第一部分,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原始碼解讀》