Node.js Async 函數最佳實踐

Node.js Async 函數最佳實踐

Node.js7.6起, Node.js 搭載了有async函數功能的V8引擎。當Node.js 8於10月31日成爲LTS版本後,咱們沒有理由不使用async函數。接下來,我將簡要介紹async函數,以及如何改變咱們編寫Node.js應用程序的方式。javascript

什麼是async函數

async函數可讓你編寫基於Promise的代碼使他像同步的同樣。每當你用asnyc關鍵字定義了一個函數體,你能夠在函數體內使用 await關鍵字。當這個async函數被調用,會獲得一個Promise實例。當async函數返回值時這個promise會執行。若是async函數拋出錯誤,那麼會進入promise的rejected流程。java

await關鍵字能夠用來等待Promise 進入 resolved並有完成返回值。若是傳給await的值不是Promise實例,它會被轉爲 Promiseresolved 流程。node

const rp = require('request-promise');
    
    async function main () {
        const result = await rp('https://google.com');
        const twenty = await 20;
    
        // sleeeeeeeeping  for a second
        await new Promise (resolve => {
        setTimeout(resolve, 1000);
        });
        
        return result
    }

    main()
        .then(console.log)
        .catch(console.error);

遷移到async函數

若是你的Node.js應用已經使用了Promise, 你只須要使用 await替代Promise鏈式調用。若是你的代碼是基於 callback, 遷移到 async函數須要逐步修改現有代碼。能夠在新到功能中使用新的技術,若是必須保留舊有功能則可使用 Promise 進行簡單的包裝。爲此你可使用內置的util.promisify(譯者注:Node.js 8.0+) 方法!git

const util = require('util');
    const {readFile} = require('fs');
    const readFileAsync = util.promisify(readFile);
    
    async function main () {
      const result = await readFileAsync('.gitignore');
    
      return result
    }
    
    main()
      .then(console.log)
      .catch(console.error);

async函數最佳實踐

express中使用async函數

As express supports Promises out of the box, using async functions with express is as simple as:github

express 是支持 Promise的,因此使用async函數能夠把代碼簡化爲:數據庫

const express = require('express');
    const app = express();
    
    app.get('/', async (request, response) => {
        // awaiting Promises here
        // if you just await a single promise, you could simply return with it,
        // no need to await for it
        const result = await getContent();
        
        response.send(result);
    });
    
    app.listen(process.env.PORT);

Edit1:如Keith Smith所指出的那樣,上面的例子有一個嚴重的問題 - 若是Promise進入rejected,express路由處理程序就會hang住,由於那裏沒有錯誤處理。express

要解決這個問題,你應該把你的異步處理程序封裝在一個處理錯誤的函數中:promise

const awaitHandlerFactory = middleware => {
    
        return async (req, res, next) => {
            try {
              await middleware(req, res, next)
            } catch (err) {
              next(err)
            }
        }
    }
    
    // and use it this way:
    app.get('/', awaitHandlerFactory(async (request, response) => {
        const result = await getContent();
        
        response.send(result);
    }));

並行

假設你正在作相似的事情,當一個操做須要兩個輸入,一個來自數據庫,另外一個來自外部服務:app

async function main () {
      const user = await Users.fetch(userId);
      const product = await Products.fetch(productId);
    
      await makePurchase(user, product);
    }

在這個case中,將會發生如下狀況:異步

  • 你的代碼將首先得到用戶資源,
  • 而後得到產品資源,
  • 並最終進行購買。

正如所見,你能夠同時作前兩個操做,由於它們之間沒有依賴關係。 爲此應該使用 Promise.all 方法:

async function main () {
        const [user, product] = await Promise.all([
            Users.fetch(userId),
            Products.fetch(productId)
        ]);
        
        await makePurchase(user, product);
    }

在某些狀況下,您只須要最快resolving獲得Promise的結果 - 在這種狀況時可使用Promise.race方法。

錯誤處理

參考下面的代碼

async function main () {
        await new Promise((resolve, reject) => {
            reject(new Error('💥'));
        });
    };
    
    main()
        .then(console.log);

若是運行這段代碼,你會在terminal上看到相似的消息:

(node:69738) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: 💥
    (node:69738) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

在新版的Node.js中,若是Promise拒毫不會被處理,那麼會致使整個Node.js進程崩潰。 所以在必要時應該使用try-catch語句:

const util = require('util');
    
    async function main () {
        try {
            await new Promise((resolve, reject) => {
              reject(new Error(' '));
            });
        } 
        catch (err) {
        // handle error case
        // maybe throwing is okay depending on your use-case
        }
    }
    
    main()
        .then(console.log)
        .catch(console.error);

可是若是使用try-catch會丟失重要的異常如系統錯誤,那麼就要從新拋出異常。 要了解更多關於何時應該從新投擲的信息,我強烈建議閱讀Eran的Learning to Throw Again.

複雜的控制流程

Node.js的第一個異步控制流庫是由 Caolan McMahon 編寫的一async的異步控制流庫。 它提供了多個異步助手:

  • mapLimit,
  • filterLimit,
  • concatLimit,
  • priorityQueue.

若是你不想從新造輪子,並且不想使用這個庫,那麼能夠重度使用 async函數和 util .promisify方法:

const util = require('util');
    const async = require('async');
    const numbers = [
        1, 2, 3, 4, 5
    ];

    mapLimitAsync = util.promisify(async.mapLimit);
    
    async function main () {
        return await mapLimitAsync(numbers, 2, (number, done) => {
            setTimeout(function () {
                done(null, number * 2);
            }, 100)
        });
    };
    
    main()
        .then(console.log)
        .catch(console.error);

原文地址


相關文章
相關標籤/搜索