深入理解 JavaScript 異步系列(2)—— jquery的解決方案


第一部分,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對象,這個對象就帶有donefail的方法,並且是等着請求返回之后再去調用

改進之后的好處

這是一個標志性的改造,不管這個概念是誰最先提出的,它在 jquery 中首先大量使用並讓全球開發者都知道原來 ajax 請求還可以這樣寫。這為以后的Promise標准制定提供了很大意義的參考,你可以以為這就是后面Promise的原型。

記住一句話————雖然 JS 是異步執行的語言,但是人的思維是同步的————因此,開發者總是在尋求如何使用邏輯上看似同步的代碼來完成 JS 的異步請求。而 jquery 的這一次更新,讓開發者在一定程度上得到了這樣的好處。

之前無論是什么操作,我都需要一股腦寫到callback中,現在不用了。現在成功了就寫到done中,失敗了就寫到fail中,如果成功了有多個步驟的操作,那我就寫很多個done,然后鏈式連接起來就 OK 了。

和后來的Promise的關系

以上的這段代碼,我們還可以這樣寫。即不用donefail函數,而是用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可以使用類似當前Promisethen函數以及鏈式操作。那么它到底是如何實現的呢?在此之前所用到的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 failthen方法(不明白的去看上一節)
  • 重新定義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 failthen方法(上一節說過),它還有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 dtddtd是一個deferred對象,而dtd.promise就是一個promise對象。

promise對象和deferred對象最重要的區別,記住了————promise對象相比於deferred對象,缺少了.resolve.reject這倆函數屬性。這么一來,可就完全不一樣了。

上一節我們提到一個問題,就是在程序的最后一行加一句w.reject()會導致亂套,你現在再在最后一行加w.reject()試試 ———— 保證亂套不了 ———— 而是你的程序不能執行,直接報錯。因為,wpromise對象,不具備.reject屬性。

返回promise的好處

上一節提到deferred對象有兩組屬性函數,而且提到應該把這兩組徹底分開。現在通過上面一行代碼的改動,就分開了。

  • waitHandle函數內部,使用dtd.resolve()來該表狀態,做主動的修改操作
  • waitHandle最終返回promise對象,只能去被動監聽變化(then函數),而不能去主動修改操作

一個“主動”一個“被動”,完全分開了。

promise 的概念

jquery v1.5 版本發布時間距離現在(2017年初春)已經老早之前了,那會兒大家網頁標配都是 jquery 。無論里面的deferredpromise這個概念和想法最早是哪位提出來的,但是最早展示給全世界開發者的是 jquery ,這算是Promise這一概念最先的提出者。

其實本次課程主要是給大家分析 ES6 的Promise Generatorasync-await,但是為何要從 jquery 開始(大家現在用 jquery 越來越少)?就是要給大家展示一下這段歷史的一些起點和發展的知識。有了這些基礎,你再去接受最新的概念會非常容易,因為所有的東西都是從最初順其自然發展進化而來的,我們要去用一個發展進化的眼光學習知識,而不是死記硬背。

求打賞

如果你看完了,感覺還不錯,歡迎給我打賞 ———— 以激勵我更多輸出優質內容

最后,github地址是 https://github.com/wangfupeng1988/js-async-tutorial 歡迎 star 和 pr

-------

學習作者教程:《前端JS高級面試》《前端JS基礎面試題》《React.js模擬大眾點評webapp》《zepto設計與源碼分析》《json2.js源碼解讀


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM