JS異步處理三強爭霸賽

前言

衆所周知 Javascript 是單線程模型語言,同時只能執行一個任務,其餘任務都必須在後面排隊等待。git

所以,異步處理就成爲 Javascript 處理多任務時提高效率最重要的方式之一,這也是 Javascript 區別於其餘語言的重要特徵。github

本文將經過對比三種目前最流行的異步處理方式,讓讀者深入體會不一樣的異步處理方式的優缺點,進一步感覺 Javascript 異步處理的獨特魅力。shell

三強選手

1. callback

首先登場的是 callback(回調函數),它是異步操做最基本的方法。編程

callback 的具體介紹以下。json

下面是兩個函數f1f2,編程的意圖是f2必須等到f1執行完成,才能執行。promise

function f1() {
  // ...
}

function f2() {
  // ...
}

f1();
f2();
複製代碼

上面代碼的問題在於,若是f1是異步操做,f2會當即執行,不會等到f1結束再執行。bash

這時,能夠考慮改寫f1,把f2寫成f1的回調函數。服務器

function f1(callback) {
  // ...
  callback();
}

function f2() {
  // ...
}

f1(f2);
複製代碼

回調函數的優勢是簡單、容易理解和實現,缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合,使得程序結構混亂、流程難以追蹤(尤爲是多個回調函數嵌套的狀況),並且每一個任務只能指定一個回調函數。異步

2. Promise

二號選手是 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的難點。

3. async

最後一位選手是asyncasync 函數是什麼?一句話,它就是 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函數的最大特色,就是能夠將異步處理編寫成同步處理,可讀性是最強的。相應的,它對開發者的理解難度和技能要求也是最高的。

比賽規則

1. 比賽內容

經過js代碼依次讀取files文件夾中的三個文件——a.jsonb.jsonc.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.jsonnext獲取c.json,這是最典型的js異步處理的問題之一。

2. 比賽環境

Node.js是服務器端運行Javascript的重要環境,能夠爲js提供讀取文件的接口,所以,本次比賽也將在Node.js(8.0以上)的環境下進行。

Node.js提供的比賽工具以下:

const fs = require('fs') // 文件讀寫接口,獲取json文件的內容
const path = require('path') // 文件路徑接口,獲取files文件夾裏面三個文件的文件路徑
複製代碼

比賽結果

1. callback

首先是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' }

複製代碼

2. promise

接下來是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' }
複製代碼

3. async

最後登場的是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…

祝工做順利,生活幸福。

相關文章
相關標籤/搜索