這就是所謂的JavaScript 異步!

編程語言 JavaScript Node.js JSON GitHub 山大王做前端 2018-11-30
ECMAScript 6(簡稱ES6)將 JavaScript 異步編程帶入了一個全新的階段。這篇文章的主題,就是介紹更強大、更完善的 ES6 異步編程方法。

首先我們回顧一下javascript異步的發展歷程。

ES6 以前:

回調函數(callback):nodejs express 中常用,ajax中常用。

ES6:

promise對象:nodejs最早有bluebird promise的雛形,axios中常用。

generator函數:nodejs koa框架使用率很高。

ES7:

async/await語法:當前最常用的異步語法,nodejs koa2 完全使用該語法。


什麼是異步

所謂"異步",簡單說就是一個任務分成兩段,先執行第一段,然後轉而執行其他任務,等做好了準備,再回過頭執行第二段。比如,有一個任務是讀取文件進行處理,異步的執行過程就是下面這樣。

這就是所謂的JavaScript 異步!

異步

上圖中,任務的第一段是向操作系統發出請求,要求讀取文件。然後,程序執行其他任務,等到操作系統返回文件,再接著執行任務的第二段(處理文件)。

這種不連續的執行,就叫做異步。相應地,連續的執行,就叫做同步。

這就是所謂的JavaScript 異步!

同步

上圖就是同步的執行方式。由於是連續執行,不能插入其他任務,所以操作系統從硬盤讀取文件的這段時間,程序只能乾等著。

回調函數callback

JavaScript 語言對異步編程的實現,就是回調函數。所謂回調函數,就是把任務的第二段單獨寫在一個函數裡面,等到重新執行這個任務的時候,就直接調用這個函數。它的英語名字 callback,直譯過來就是"重新調用"。

回調字面也好理解,就是先處理本體函數,再處理回調的函數,舉個例子,方便大家理解。

這就是所謂的JavaScript 異步!

上面的例子很好理解,首先執行主體函數A,打印結果:我是主題函數;

然後執行回調函數callback 也就是B,打印結果:我是回調函數。

promise對象

promise 對象用於一個異步操作的最終完成(或最終失敗)及其結果的表示。

簡單地說就是處理一個異步請求。我們經常會做些斷言,如果我贏了你就嫁給我,如果輸了我就嫁給你之類的斷言。

這就是promise的中文含義:斷言,一個成功,一個失敗。

舉個例子,方便大家理解:

promise構造函數的參數是一個函數,我們把它稱為處理器函數。

處理器函數接收兩個函數reslove和reject作為其參數,當異步操作順利執行則執行reslove函數, 當異步操作中發生異常時,則執行reject函數。

通過resolve傳入得的值,可以在then方法中獲取到,通過reject傳入的值可以在chatch方法中獲取到。

因為then和catch都返回一個相同的promise對象,所以可以進行鏈式調用。

這就是所謂的JavaScript 異步!

Promise 的寫法只是回調函數的改進,使用then方法以後,異步任務的兩段執行看得更清楚了,除此以外,並無新意。

Promise 的最大問題是代碼冗餘,原來的任務被Promise 包裝了一下,不管什麼操作,一眼看去都是一堆 then,原來的語義變得很不清楚。

那麼,有沒有更好的寫法呢?

協程

傳統的編程語言,早有異步編程的解決方案(其實是多任務的解決方案)。其中有一種叫做"協程"(coroutine),意思是多個線程互相協作,完成異步任務。

協程有點像函數,又有點像線程。它的運行流程大致如下。

第一步,協程A開始執行。

第二步,協程A執行到一半,進入暫停,執行權轉移到協程B。

第三步,(一段時間後)協程B交還執行權。

第四步,協程A恢復執行。

上面流程的協程A,就是異步任務,因為它分成兩段(或多段)執行。

舉例來說,讀取文件的協程寫法如下。

function asnycJob() {
// ...其他代碼
var f = yield readFile(fileA);
// ...其他代碼
}

上面代碼的函數 asyncJob 是一個協程,它的奧妙就在其中的 yield 命令。它表示執行到此處,執行權將交給其他協程。也就是說,yield命令是異步兩個階段的分界線。

協程遇到 yield 命令就暫停,等到執行權返回,再從暫停的地方繼續往後執行。它的最大優點,就是代碼的寫法非常像同步操作,如果去除yield命令,簡直一模一樣。

Generator 函數

Generator 函數是協程在 ES6 的實現,最大特點就是可以交出函數的執行權(即暫停執行)。

function* gen(x){
var y = yield x + 2;
return y;
}

上面代碼就是一個 Generator 函數。它不同於普通函數,是可以暫停執行的,所以函數名之前要加星號,以示區別。

整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操作需要暫停的地方,都用 yield 語句註明。Generator 函數的執行方法如下。

var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }

上面代碼中,調用 Generator 函數,會返回一個內部指針(即遍歷器 )g 。這是 Generator 函數不同於普通函數的另一個地方,即執行它不會返回結果,返回的是指針對象。調用指針 g 的 next 方法,會移動內部指針(即執行異步任務的第一段),指向第一個遇到的 yield 語句,上例是執行到 x + 2 為止。

換言之,next 方法的作用是分階段執行 Generator 函數。每次調用 next 方法,會返回一個對象,表示當前階段的信息( value 屬性和 done 屬性)。value 屬性是 yield 語句後面表達式的值,表示當前階段的值;done 屬性是一個布爾值,表示 Generator 函數是否執行完畢,即是否還有下一個階段。

Generator 函數的用法

下面看看如何使用 Generator 函數,執行一個真實的異步任務。

var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}

上面代碼中,Generator 函數封裝了一個異步操作,該操作先讀取一個遠程接口,然後從 JSON 格式的數據解析信息。就像前面說過的,這段代碼非常像同步操作,除了加上了 yield 命令。

執行這段代碼的方法如下。

var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});

上面代碼中,首先執行 Generator 函數,獲取遍歷器對象,然後使用 next 方法(第二行),執行異步任務的第一階段。由於 Fetch 模塊返回的是一個 Promise 對象,因此要用 then 方法調用下一個next 方法。

可以看到,雖然 Generator 函數將異步操作表示得很簡潔,但是流程管理卻不方便(即何時執行第一階段、何時執行第二階段)。

async-await

async函數返回一個promise對象,如果在async函數中返回一個直接量,async會通過Promise.resolve封裝成Promise對象。

我們可以通過調用promise對象的then方法,獲取這個直接量。

這就是所謂的JavaScript 異步!

那如過async函數不返回值,又會是怎麼樣呢?

這就是所謂的JavaScript 異步!

await會暫停當前async的執行,await會阻塞代碼的執行,直到await後的表達式處理完成,代碼才能繼續往下執行。

await後的表達式既可以是一個Promise對象,也可以是任何要等待的值。

如果await等到的是一個 Promise 對象,await 就忙起來了,它會阻塞後面的代碼,等著 Promise 對象 resolve,然後得到 resolve 的值,作為 await 表達式的運算結果。

上邊你看到阻塞一詞,不要驚慌,async/await只是一種語法糖,代碼執行與多個callback嵌套調用沒有區別。

本質並不是同步代碼,它只是讓你思考代碼邏輯的時候能夠以同步的思維去思考,避開回調地獄。

簡而言之-async/await是以同步的思維去寫異步的代碼,所以async/await並不會影響node的併發數,大家可以大膽的應用到項目中去!

如果它等到的不是一個 Promise 對象,那 await 表達式的運算結果就是它等到的東西。

舉個例子,方便大家理解:

這就是所謂的JavaScript 異步!

編程是一種修行,我願與志同道合的朋友攜手前行,一起探索有關編程的奧妙!

如果您在前端學習的過程中遇到難題,歡迎【關注】並【私信】我,大家一起交流解決!

推薦文章:

不一樣的JS函數總結,適合前端初學者的JavaScript函數代碼

原來這就是JS箭頭函數!適合新手入門的前端JavaScript代碼(上)

JS函數聲明和函數表達式的定義及其區別——超詳講解,值得擁有

相關推薦

推薦中...