衆所周知 Javascript
是單線程模型語言,同時只能執行一個任務,其餘任務都必須在後面排隊等待。git
所以,異步處理就成爲 Javascript
處理多任務時提高效率最重要的方式之一,這也是 Javascript
區別於其餘語言的重要特徵。github
本文將經過對比三種目前最流行的異步處理方式,讓讀者深入體會不一樣的異步處理方式的優缺點,進一步感覺 Javascript
異步處理的獨特魅力。shell
首先登場的是 callback
(回調函數),它是異步操做最基本的方法。編程
callback
的具體介紹以下。json
下面是兩個函數f1
和f2
,編程的意圖是f2
必須等到f1
執行完成,才能執行。promise
function f1() {
// ...
}
function f2() {
// ...
}
f1();
f2();
複製代碼
上面代碼的問題在於,若是f1
是異步操做,f2
會當即執行,不會等到f1
結束再執行。bash
這時,能夠考慮改寫f1
,把f2
寫成f1
的回調函數。服務器
function f1(callback) {
// ...
callback();
}
function f2() {
// ...
}
f1(f2);
複製代碼
回調函數的優勢是簡單、容易理解和實現,缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合,使得程序結構混亂、流程難以追蹤(尤爲是多個回調函數嵌套的狀況),並且每一個任務只能指定一個回調函數。異步
二號選手是 Promise
,它是異步編程的一種新的解決方案。async
所謂
Promise
,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise
是一個對象,從它能夠獲取異步操做的消息。Promise
提供統一的API
,各類異步操做均可以用一樣的方法進行處理。
下面是一個Promise
對象的簡單例子。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
複製代碼
Promise
解決了callbak
回調地獄的問題,異步處理的表達流程也更加清晰,可是多層嵌套帶來的繁瑣寫法,也是困擾Promise
的難點。
最後一位選手是async
,async
函數是什麼?一句話,它就是 Generator
函數的語法糖。
具體代碼展現以下。
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
複製代碼
async
函數的最大特色,就是能夠將異步處理編寫成同步處理,可讀性是最強的。相應的,它對開發者的理解難度和技能要求也是最高的。
經過js
代碼依次讀取files
文件夾中的三個文件——a.json
、b.json
、c.json
的內容。
a.json
的內容以下:
{
"next":"b.json",
"msg":"this is a"
}
複製代碼
b.json
的內容以下:
{
"next":"c.json",
"msg":"this is b"
}
複製代碼
c.json
的內容以下:
{
"next": null,
"msg":"this is c"
}
複製代碼
首先讀取a.json
的內容,而後獲取next
再讀取b.json
,最後讀取b.json
的next
獲取c.json
,這是最典型的js
異步處理的問題之一。
Node.js
是服務器端運行Javascript
的重要環境,能夠爲js
提供讀取文件的接口,所以,本次比賽也將在Node.js
(8.0以上)的環境下進行。
Node.js
提供的比賽工具以下:
const fs = require('fs') // 文件讀寫接口,獲取json文件的內容
const path = require('path') // 文件路徑接口,獲取files文件夾裏面三個文件的文件路徑
複製代碼
首先是callback
登場,它給出的解決方式以下:
// callback 方式獲取一個文件的內容
function getFileContentByCallback(fileName, callback) {
// path 獲取文件路徑
const fullFileName = path.resolve(__dirname, 'files', fileName)
// fs 讀取文件
fs.readFile(fullFileName, (err, data) => {
if (err) {
console.error(err)
return
}
callback(
JSON.parse(data.toString())
)
})
}
// 讀取文件
getFileContentByCallback('a.json', aData => {
console.log('a data callback: ', aData)
getFileContentByCallback(aData.next, bData => {
console.log('b data callback: ', bData)
getFileContentByCallback(bData.next, cData => {
console.log('c data callback: ', cData)
})
})
})
// 輸出結果
a data callback: { next: 'b.json', msg: 'this is a' }
b data callback: { next: 'c.json', msg: 'this is b' }
c data callback: { next: null, msg: 'this is c' }
複製代碼
接下來是promise
登場,它的解決方式以下:
// promise 方式獲取一個文件的內容
function getFileContentByPromise(fileName) {
const promise = new Promise((resolve, reject) => {
// path 獲取文件路徑
const fullFileName = path.resolve(__dirname, 'files', fileName)
// fs 讀取文件
fs.readFile(fullFileName, (err, data) => {
if (err) {
reject(err)
return
}
resolve(
JSON.parse(data.toString())
)
})
})
return promise
}
// 讀取文件
getFileContentByPromise('a.json').then(aData => {
console.log('a data promise: ', aData)
return getFileContentByPromise(aData.next)
}).then(bData => {
console.log('b data promise: ', bData)
return getFileContentByPromise(bData.next)
}).then(cData => {
console.log('c data promise: ', cData)
})
// 輸出結果
a data promise: { next: 'b.json', msg: 'this is a' }
b data promise: { next: 'c.json', msg: 'this is b' }
c data promise: { next: null, msg: 'this is c' }
複製代碼
最後登場的是async
,它的解決方式以下:
// async 方式獲取一個文件的內容
async function getFileContentByAsync(fileName) {
try {
// getFileContentByPromise 是上面promise獲取文件內容的處理函數,aysnc直接調用
const aData = await getFileContentByPromise(fileName)
const bData = await getFileContentByPromise(aData.next)
const cData = await getFileContentByPromise(bData.next)
console.log('a data async: ', aData)
console.log('b data async: ', bData)
console.log('c data async: ', cData)
} catch (error) {
console.error(error)
}
}
// 讀取文件
getFileContentByAsync('a.json')
// 輸出結果
a data async: { next: 'b.json', msg: 'this is a' }
b data async: { next: 'c.json', msg: 'this is b' }
c data async: { next: null, msg: 'this is c' }
複製代碼
從比賽結果來看,它們均可以順利完成任務,從實現方式來看,卻各不相同。
本次比賽並不是比較三者到底誰更強,而是經過最直觀的對比方式,讓你們感覺js
異步處理的多樣性,以及每種異步處理的特色和差別性。
callback
利於理解、學習成本低,Promise
承上啓下、中流砥柱,async
集大成者、表明將來。
最後附上比賽的GitHub地址:github.com/jiangjiahen…
祝工做順利,生活幸福。