JavaScript >> Javascript 文檔 >  >> Node.js

ES7 中的 Node.js 異步等待

JavaScript(以及 Node.js)最令人興奮的特性之一是 async /await ES7 中引入的語法。雖然它基本上只是 Promises 之上的語法糖,但僅這兩個關鍵字就應該讓在 Node 中編寫異步代碼更容易接受。這幾乎消除了回調地獄的問題,甚至讓我們在異步代碼周圍使用控制流結構。

在本文中,我們將看看 Promises 有什麼問題,新的 await 功能可以提供幫助,以及如何開始使用它現在 .

Promise 的問題

JavaScript 中“承諾”的概念已經存在了一段時間,並且由於 Bluebird 和 q 等第三方庫,它已經使用了多年,更不用說最近在 ES6 中添加的原生支持了。

它們是回調地獄問題的一個很好的解決方案,但不幸的是它們並沒有解決所有的異步問題。雖然是一個很大的改進,但 Promise 讓我們想要更加簡化。

假設您想使用 Github 的 REST API 來查找項目的星數。在這種情況下,您可能會使用出色的 request-promise 庫。使用基於 Promise 的方法,您必鬚髮出請求並在傳遞給 .then() 的回調中返回結果 ,像這樣:

var request = require('request-promise');

var options = {
    url: 'https://api.github.com/repos/scottwrobinson/camo',
    headers: {
        'User-Agent': 'YOUR-GITHUB-USERNAME'
    }
};

request.get(options).then(function(body) {
    var json = JSON.parse(body);
    console.log('Camo has', json.stargazers_count, 'stars!');
});

這將打印出如下內容:

$ node index.js
Camo has 1,000,000 stars!

好吧,也許這個數字有點誇張,但你明白了;)

使用 Promises 只發出一個這樣的請求並不難,但是如果我們想對 GitHub 上的許多不同存儲庫發出相同的請求怎麼辦?如果我們需要圍繞請求添加控制流(如條件或循環)會發生什麼?隨著您的需求變得越來越複雜,Promise 變得越來越難以使用,並且最終仍然會使您的代碼變得複雜。它們仍然比普通回調更好,因為你沒有無限嵌套,但它們並不能解決你所有的問題。

對於更複雜的場景,如以下代碼中的場景,您需要善於將 Promises 鏈接在一起並了解何時何地 你的異步代碼被執行。

"use strict";

var request = require('request-promise');

var headers = {
    'User-Agent': 'YOUR-GITHUB-USERNAME'
};

var repos = [
    'scottwrobinson/camo',
    'facebook/react',
    'scottwrobinson/twentyjs',
    'moment/moment',
    'nodejs/node',
    'lodash/lodash'
];

var issueTitles = [];

var reqs = Promise.resolve();

repos.forEach(function(r) {
    var options = { url: 'https://api.github.com/repos/' + r, headers: headers };

    reqs = reqs.then(function() {
        return request.get(options);
    }).then(function(body) {
        var json = JSON.parse(body);

        var p = Promise.resolve();

        // Only make request if it has open issues
        if (json.has_issues) {
            var issuesOptions = { url: 'https://api.github.com/repos/' + r + '/issues', headers: headers };
            p = request.get(issuesOptions).then(function(ibody) {
                var issuesJson = JSON.parse(ibody);

                if (issuesJson[0]) {
                    issueTitles.push(issuesJson[0].title);
                }
            });
        }

        return p;
    });
});

reqs.then(function() {
    console.log('Issue titles:');
    issueTitles.forEach(function(t) {
        console.log(t);
    });
});

注意 :Github 積極地對未經身份驗證的請求進行速率限制,所以如果您在運行上述代碼幾次後被切斷,請不要感到驚訝。您可以通過傳遞客戶端 ID/秘密來增加此限制。

在撰寫本文時,執行此代碼將產生以下結果:

$ node index.js
Issue titles:
feature request: bulk create/save support
Made renderIntoDocument tests asynchronous.
moment issue template
test: robust handling of env for npm-test-install

只需添加一個 for 循環和一個 if 對我們的異步代碼的聲明使它更難閱讀和理解。這種複雜性只能維持很長時間,然後才會變得難以處理。

查看代碼,您能立即告訴我請求實際執行的位置,或者每個代碼塊的運行順序嗎?可能沒有仔細閱讀它。

使用 Async/Await 進行簡化

新的 async /await 語法允許您仍然使用 Promises,但它消除了為鏈接的 then() 提供回調的需要 方法。將發送到 then() 的值 回調直接從異步函數返回,就好像它是一個同步阻塞函數一樣。

let value = await myPromisifiedFunction();

雖然看似簡單,但這是對異步 JavaScript 代碼設計的巨大簡化。實現這一點所需的唯一額外語法是 await 關鍵詞。因此,如果您了解 Promises 的工作原理,那麼理解如何使用這些新關鍵字就不會太難,因為它們建立在 Promises 的概念之上。你真正需要知道的是 任何 Promise 都可以是 await -ed .值也可以是 await -ed,就像 Promise 可以 .resolve() 在整數或字符串上。

讓我們比較一下基於 Promise 的方法和 await 關鍵詞:

承諾

免費電子書:Git Essentials

查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!

var request = require('request-promise');

request.get('https://api.github.com/repos/scottwrobinson/camo').then(function(body) {
    console.log('Body:', body);
});

等待

var request = require('request-promise');

async function main() {
    var body = await request.get('https://api.github.com/repos/scottwrobinson/camo');
    console.log('Body:', body);
}
main();

如您所見,await 表示您要解析 Promise 並且 不返回實際的 Promise 對象 像往常一樣。執行此行時,request call 將被放入事件循環的堆棧中,執行將讓給其他準備好處理的異步代碼。

async 定義包含異步代碼的函數時使用關鍵字。這是一個從函數返回 Promise 的指標,因此應該被視為異步的。

下面是一個簡單的用法示例(注意函數定義的變化):

async function getCamoJson() {
    var options = {
        url: 'https://api.github.com/repos/scottwrobinson/camo',
        headers: {
            'User-Agent': 'YOUR-GITHUB-USERNAME'
        }
    };
    return await request.get(options);
}

var body = await getCamoJson();

現在我們知道如何使用 asyncawait 一起來看看之前更複雜的基於 Promise 的代碼現在是什麼樣子:

"use strict";

var request = require('request-promise');

var headers = {
    'User-Agent': 'scottwrobinson'
};

var repos = [
    'scottwrobinson/camo',
    'facebook/react',
    'scottwrobinson/twentyjs',
    'moment/moment',
    'nodejs/node',
    'lodash/lodash'
];

var issueTitles = [];

async function main() {
    for (let i = 0; i < repos.length; i++) {
        let options = { url: 'https://api.github.com/repos/' + repos[i], headers: headers };
        let body = await request.get(options);
        let json = JSON.parse(body);

        if (json.has_issues) {
            let issuesOptions = { url: 'https://api.github.com/repos/' + repos[i] + '/issues', headers: headers };
            let ibody = await request.get(issuesOptions);
            let issuesJson = JSON.parse(ibody);

            if (issuesJson[0]) {
                issueTitles.push(issuesJson[0].title);
            }
        }
    }

    console.log('Issue titles:');
    issueTitles.forEach(function(t) {
        console.log(t);
    });
}

main();

它現在肯定更具可讀性,因為它可以像許多其他線性執行語言一樣編寫。

現在唯一的問題是每個 request.get() call 是串行執行的(意味著每個調用都必須等到前一個調用完成才能執行),因此我們必須等待更長的時間讓代碼完成執行才能獲得結果。更好的選擇是並行運行 HTTP GET 請求。這仍然可以通過利用 Promise.all() 來完成 就像我們以前會做的那樣。只需替換 for 使用 .map() 循環 調用並將生成的 Promises 數組發送到 Promise.all() ,像這樣:

// Init code omitted...

async function main() {
    let reqs = repos.map(async function(r) {
        let options = { url: 'https://api.github.com/repos/' + r, headers: headers };
        let body = await request.get(options);
        let json = JSON.parse(body);

        if (json.has_issues) {
            let issuesOptions = { url: 'https://api.github.com/repos/' + r + '/issues', headers: headers };
            let ibody = await request.get(issuesOptions);
            let issuesJson = JSON.parse(ibody);

            if (issuesJson[0]) {
                issueTitles.push(issuesJson[0].title);
            }
        }
    });

    await Promise.all(reqs);
}

main();

這樣您就可以利用並行執行的速度 await 的簡單性 .

除了能夠使用循環和條件等傳統控制流之外,還有更多好處。這種線性方法讓我們回到使用 try...catch 處理錯誤的語句。使用 Promises 你必須使用 .catch() 方法,該方法有效,但可能會導致混淆,以確定它為哪些 Promise 捕獲了異常。

所以現在這個...

request.get('https://api.github.com/repos/scottwrobinson/camo').then(function(body) {
    console.log(body);
}).catch(function(err) {
    console.log('Got an error:', err.message);
});

// Got an error: 403 - "Request forbidden by administrative rules. Please make sure your request has a User-Agent header..."

...可以這樣表達:

try {
    var body = await request.get('https://api.github.com/repos/scottwrobinson/camo');
    console.log(body);
} catch(err) {
    console.log('Got an error:', err.message)
}

// Got an error: 403 - "Request forbidden by administrative rules. Please make sure your request has a User-Agent header..."

雖然它的代碼量大致相同,但對於從另一種語言過渡到 JavaScript 的人來說,它更容易閱讀和理解。

立即使用異步

異步功能仍處於提案階段,但不用擔心,您仍然可以通過多種方式在代碼中使用它現在 .

V8

雖然它還沒有完全進入 Node,但 V8 團隊已經公開表示他們打算實現 async /await 特徵。他們甚至已經提交了原型運行時實現,這意味著和諧支持應該不會落後太多。

通天塔

可以說,最流行的選擇是使用 Babel 及其各種插件來轉譯您的代碼。 Babel 非常受歡迎,因為它能夠使用他們的插件系統混合和匹配 ES6 和 ES7 功能。雖然設置起來有點複雜,但它也為開發人員提供了更多的控制權。

再生器

Facebook 的 regenerator 項目沒有 Babel 那麼多功能,但它是一種更簡單的異步轉譯工作方式。

我遇到的最大問題是它的錯誤不是很具有描述性。因此,如果您的代碼中存在語法錯誤,您將無法從 regenerator 中獲得太多幫助來查找它。除此之外,我一直很滿意。

跟踪器

我個人對此沒有任何經驗,但 Traceur(由 Google 提供)似乎是另一種流行的選擇,具有許多可用功能。您可以在此處找到更多信息,了解有關可以轉譯哪些 ES6 和 ES7 功能的詳細信息。

asyncawait

您可以使用的大多數選項都涉及轉譯或使用 V8 的夜間構建來獲得 async 在職的。另一種選擇是使用 asyncawait 包,它提供了一個類似於 await 的解析 Promises 的函數 特徵。這是一種很好的原生 ES5 方法,可以獲取外觀相似的語法。

結論

就是這樣!就個人而言,我對 ES7 中的這個特性感到最興奮,但 ES7 中還有一些其他很棒的特性你應該看看,比如類裝飾器和屬性。

你使用轉譯的 ES7 代碼嗎?如果是這樣,哪個功能對您的工作最有益?請在評論中告訴我們!


Tutorial JavaScript 教程
  1. 代碼 100 天的第 3 天

  2. 我的數字輸入中的值不會隨著用戶交互而改變[關閉]

  3. 使用 Jest、Sinon 和 Typescript 模擬 Node-Fetch

  4. 如何修正npm/yarn的security問題

  5. Hapi.js 中的擴展點

  6. 使用 Storybook 構建 React 應用程序

  7. 如何使用 JavaScript 獲取光標下的單詞?

  1. 註冊表單的 JavaScript 驗證 | HTML 示例代碼

  2. Hve Notes - 靜態博客寫作客戶端

  3. 將 Redux 與 React 結合使用

  4. 使用 RxJS 的簡單倒計時

  5. 如何在 TypeScript 中創建接口

  6. 函數式編程構建塊

  7. React-Spring Into

  1. 在 Vue 中使用 GSAP 進行補間

  2. 在 Twitch 上實時編碼重構 Node.js (JavaScript) 比特幣 Twitter Bot

  3. 下一個 VueJS 項目的 5 個 Vuex 插件

  4. 如何使用 Docker 構建 Node.js 應用程序