不使用回調函數的ajax請求實現(async和await簡化回調函數嵌套)


在常規的服務器端程序設計中, 比如說爬蟲程序, 發送http請求的過程會使整個執行過程阻塞,直到http請求響應完成代碼才會繼續執行, 以php為例子

$url = "http://www.google.com.hk";
$result = file_get_contents($url);
echo $result;

當代碼執行到第二行時,程序便陷入了等待,直到請求完成,程序才會繼續往下跑將抓取到的html輸出。這種做法的好處是代碼簡潔明了,運行流程清晰, 容易維護。 缺點就是程序的運行速度依賴於http請求的響應時間,影響程序的運行效率。 然而, 因為web程序本身特質的原因,這種問題是避無可避的,程序依賴於http響應的結果和保證自身的迅速響應兩者之間是存在矛盾的, 肯定無法兼顧。

但是在客戶端程序或者非http應用的場景下是不存在類似的沖突的, 在Java或C#客戶端編程中,碰到這種問題一般都是開啟兩個線程各干各的。而在JavaScript中,因為語言本身不支持多線程, 所以此類問題是使用回調函數來解決。

以最簡單的前端ajax請求為例

 $.get("data.json", function ( response ) {
        console.log("2");
 });
 console.log("1")

代碼先輸出1,再輸出2,整個程序執行流程並未因http請求而被阻塞,回調函數方案完美的把問題解決。 然而,這只是最簡單回調函數示例,假如回調函數嵌套了許多層呢?

 

 

$.get("data1.json", function (response) {
    $.get(response.url, function (response) {
        $.get(response.url, function (response) {
            console.log(response);
        });
    });
});

回調嵌套的越深,代碼運行邏輯就越難理清楚, 如果在上面代碼的基礎上再混入一些復雜的業務邏輯,那代碼將會極難維護, 到時候遇到問題了剪不斷理還亂的感覺肯定會讓人紅着眼睛罵娘。 雖然這種回調嵌套的場景在web前端開發中比較罕見, 但在nodejs服務器端開發領域還是常見的。 那如何克服這個問題?假如用php來寫, 那便是一件很輕松的事了。

$response = file_get_contents("data1.json");
$response1 =   file_get_contents($response["url"]);
$response2 =   file_get_contents($response1["url"]);
echo $response;

以php發送http請求的方案來實現, 代碼邏輯就清晰了許多。 在古時候 ,JavaScript想以這種方式實現ajax那就是痴人說夢,但是當JavaScript升級至es6版本后,通過特定的途徑也可實現這種寫法。 在網上這種寫法被稱之為“以同步的方式編寫異步代碼”,但是我覺得這種說法容易把人給搞迷糊,可以直接把這種寫法稱之為:“同步寫法”, 因為里面的異步執行已經被隱藏了起來。 要實現這種寫法必須使用async和await這兩個關鍵字。在兩個關鍵字是es7的范疇, es6還不支持,但是可以通過特定的工具將使用這兩個關鍵字的代碼轉為es6的代碼去執行, 比如說TypeScript和babel, 在此文中使用的代碼示例都是由TypeScript實現。對於async和await的底層機制這里就不詳述了, 以免將文章的篇幅拖的很長,這里就講解一下這兩個關鍵字能實現的效果。 先把上面用JavaScript實現的多層嵌套回調用同步的方式來改寫, 代碼如下

async function ajax(url) {
    return new Promise(function (resolve, reject) {
        let ajaxSetting = {
            url: url,
            success: function (response) {
                resolve(response);
            },
            error: function () {
                reject("請求失敗");
            }
        }
        $.ajax(ajaxSetting);
 
    });
}
 
async function run() {
    let response1 = await ajax("data1.json");
    let response2 = await ajax(response1["url"]);
    let response3 = await ajax(response2["url"]);
    console.log(response3);
}
 
//不阻塞
run();

代碼由ajax和run這兩個函數組成, ajax是對jquery ajax的封裝,使之能不使用回調函數就能獲得ajax的響應結果。 當函數被聲明為async類型時,如果這個函數要有返回值 ,並且返回值要在某個回調函數中獲得,那么這個函數的返回結果就只能是一個 Promise對象,就像示例的ajax函數一樣,返回值如果是其它類型那就達不到期望的效果。 Promise構造函數的參數是一個函數,resolve和reject分別是這個函數的兩個參數,同時這兩個參數自身也是函數類型,這兩個參數有着重要的意義,在這里它們的作用就是將ajax的響應內容給返回出去,resolve表示返回正常狀況下的值, reject表示返回異常狀態下的值。按照傳統的編碼方式, 可以將reject看作是拋出了一個異常,像throw "請求失敗", 這樣,在函數調用的外部可以用try catch進行捕獲。將值傳出去為什么要通過這兩個參數呢?因為沒轍啊, 試想一下,ajax的回調函數中使用return語句, 意義何在?因此也只能變向的通過Promise將返回值扔給外部的調用者。 所以,使用async和await的第一個要點就是

當函數要獲得異步結果時,可以函數聲明為async類型, 函數的返回值設為Promise類型對象,而Promise中的resolve和reject是用來向async函數返回結果的, 功效如同普通函數的return語句。

async類型函數要怎么使用呢?有兩種方法,一種是直接調用, 直接調用的話函數前面async關鍵字就被忽略了, 調用函數返回的結果就是一個Promise對象, Promise對像如何使用在這里不進行深究,大致就是像下面這樣的寫法

ajax("data1.json").then(function( response ){
……
});

還是以回調函數的形式出現,改進代碼所帶來的意義並沒有體現。

另一種方法是在調用函數時加上await關鍵字,await的意義就在於接收async函數中的Promise對象中resolve和reject傳遞的值 ,而且除非resolve和reject這兩個函數在回調函數中被調用到了, 否則代碼就一直被阻塞在那里?換句話說, resolve和reject的調用是用來通知await等待結束,代碼可以繼續執行了。 這種寫法不就是之前想方設法想實現的同步寫法么?跟php的寫法區別在於多了 await、async、Promise這三個概念, 但是在不考慮其中的內部運行原理的話, 代碼的執行流程上已經和同步的寫法沒一絲區別了。有一點需要注意, 假如需要在函數中使用await調用,那么這個函數也必須被聲明為async類型, 否則編譯出錯, 程序無法正常運行。 所以, 第二個要點就是

await就是用來等待Promise對象中resolve和reject這兩個函數的執行的,並且將這兩個函數傳遞的參數當作返回結果賦給變量,如同run函數中的代碼示例那樣。 別外, await必須被夾在兩個async中間, 一個是await調用的函數,一個是await所在的函數。

至於Promise中的reject,就是用來拋異常的, 在外await調用之外可使用try catch捕獲,代碼如下

async function run() {
    try {
        let response1 = await ajax("data1.json");
        let response2 = await ajax(response1["url"]);
        let response3 = await ajax(response2["url"]);
        console.log(response3);
    } catch (ex) {
        console.log(ex);
    }
}

此文只是純粹的講解 await和async能起什么樣的作用?如何使用?至於深入細節方面的知識, 有興趣的同學可以去阮一峰的博客里學習, 附上鏈接地址

http://www.ruanyifeng.com/blog/2015/05/async.html

 


免責聲明!

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



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