如何優雅地寫js異步代碼(2)

hubuke 8年前發布 | 20K 次閱讀 JavaScript開發 JavaScript

如何優雅地寫js異步代碼(2)

Rock with async/await

本篇文章是作為上一篇的續集,考慮到第一篇的篇幅,還有更重要的一點就是上一篇講的內容已經可以直接應用在最新版本的Node.js和一些高級瀏覽器(Chrome,FF)中,具體兼容性可參考:https://kangax.github.io/compat-table/es6/

而這一篇講的內容,是ECMAScript 2016(ES7)的async/await特性,目前的兼容性可參考:http://kangax.github.io/compat-table/esnext/#test-async_functions,雖然現在來看還不是非常樂觀,但是我們可以通過第三方的代碼轉換工具(如TraceurBabel),將這些新特性的代碼轉換為當前環境可運行的代碼。

一個簡單的例子

實現同步的sleep,同步的代碼看起來應該是下面的樣子:

function sleep(timeout) {  
    setTimeout(function() {}, timeout);
}

function main() {  
    console.time('how long did I sleep');
    sleep(3000);
    console.timeEnd('how long did I sleep');
}

main();  
// how long did I sleep: 0ms

但是在js中的執行結果卻是0ms,這不是我們預期的呀。

改造

按照這種同步的代碼流程,怎么樣才能輸出3000ms呢?看過上一篇文章的童鞋應該很快就能想到使用Generatoryield,沒看過的童鞋建議先看完上一篇再回來。

sleep(5min)

好,我就當你們都回來了,接下來就說說如何使用async/await實現“同步”的sleep。

await期望的值是一個Promise對象,改造sleep方法:

function sleep(timeout) {  
    return new Promise(function(resolve, reject) {
        setTimeout(resolve, timeout);
    });
}

除此之外,main方法還需要使用async顯式聲明成異步方法:

async function main() {  
    console.time('how long did I sleep');
    await sleep(3000);
    console.timeEnd('how long did I sleep');
}

將以上代碼保存為async-sleep.js。前面也說了需要借助第三方代碼轉換工具,那我們就安裝Babel

> npm install --save-dev babel-cli

安裝后執行:

> ./node_modules/babel-cli/bin/babel-node.js async-sleep.js

沒出意外的話,我們應該看到...WTF,出錯了

SyntaxError: async-sleep.js: Unexpected token (7:6)  
   5 | }
   6 |
>  7 | async function main() {
     |       ^
   8 |     console.time('how long did I sleep');
   9 |     await sleep(3000);
  10 |     console.timeEnd('how long did I sleep');

這時需要安裝一個Babel的插件用于轉換async

> npm install --save-dev babel-plugin-transform-async-to-generator

安裝好后,需要在運行目錄添加一個配置文件.babelrc

> vi .babelrc
{
    "plugins": ["transform-async-to-generator"]
}

或在命令中指定:

>  ./node_modules/babel-cli/bin/babel-node.js --plugins transform-async-to-generator async-sleep.js

沒出意外的話,我們應該看到...WTF,又出錯了

async-sleep.js:1  
(function (exports, require, module, __filename, __dirname) { let main = (() => {
                                                              ^^^

SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode  

好吧,提示也很明顯,我們使用strict模式,完整的代碼如下:

'use strict';

function sleep(timeout) {  
    return new Promise(function(resolve, reject) {
        setTimeout(resolve, timeout);
    });
}

async function main() {  
    console.time('how long did I sleep');
    await sleep(3000);
    console.timeEnd('how long did I sleep');
}

main();  
// how long did I sleep: 3003ms

再執行,終于看到我們期望的3003ms。呃,怎么不是3000ms,不要在意這些細節。難道QQ空間曾經用這個實際延遲的誤差來判斷客戶端CPU的繁忙程度也要告訴你。

看完這個例子,是不是發現async類似于Generator中的*,而await類似于yield,但是現在不需要再額外封裝一個run方法了,這還是很方便的。

重寫回調地獄的例子

繼續重寫那個回調地獄的例子:

  1. 讀取當前目錄的package.json
  2. 檢查backup目錄是否存在,如果不存在就創建backup目錄
  3. 將文件內容寫到備份文件

readPackageFilecheckBackupDirbackupPackageFile直接使用上一篇中的定義:

var readPackageFile = new Promise(function(resolve, reject) {  
    fs.readFile('./package.json', function(err, data) {
        if (err) {
            reject(err);
        }

        resolve(data);
    });
});

var checkBackupDir = new Promise(function(resolve, reject) {  
    fs.exists('./backup', function(exists) {
        if (!exists) {
            resolve(mkBackupDir);
        } else {
            resolve();
        }
    });
});

var mkBackupDir = new Promise(function(resolve, reject) {  
    // throw new Error('unexpected error');
    fs.mkdir('./backup', function(err) {
        if (err) {
            return reject(err);
        }

        resolve();
    });
});

function backupPackageFile(data) {  
    return new Promise(function(resolve, reject) {
        fs.writeFile('./backup/package.json', data, function(err) {
            if (err) {
                return reject(err);
            }

            resolve();
        });
    });
};

Let's Rock:

(async function() {
    try {
        // 1. 讀取當前目錄的package.json
        var data = await readPackageFile;

        // 2. 檢查backup目錄是否存在,如果不存在就創建backup目錄
        await checkBackupDir;

        // 3. 將文件內容寫到備份文件
        await backupPackageFile(data);

        console.log('backup successed');
    } catch (err) {
        console.error(err);
    }
}());

總結

js正朝著越來越好的方向發展,不是嗎?

參考地址

題圖引自:http://forwardjs.com/img/workshops/advancedjs-async.jpg

來自:http://iammapping.com/write-js-async-gracefully-2/

 本文由用戶 hubuke 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!