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

在 Node.js 中將回調轉換為 Promise

簡介

幾年前,回調是我們在 JavaScript 中實現異步代碼執行的唯一方法。回調的問題很少,最引人注目的是“回調地獄”。

在 ES6 中,承諾 被引入作為這些問題的解決方案。最後,async/await 引入關鍵字是為了獲得更愉快的體驗並提高可讀性。

即使添加了新方法,仍然有很多本地模塊和庫使用回調。在本文中,我們將討論如何將 JavaScript 回調轉換為 Promise。 ES6 的知識會派上用場,因為我們將使用諸如擴展運算符之類的功能來使事情變得更容易。

什麼是回調

回調是一個函數參數,它恰好是一個函數本身。雖然我們可以創建任何函數來接受另一個函數,但回調主要用於異步操作。

JavaScript 是一種解釋型語言,一次只能處理一行代碼。有些任務可能需要很長時間才能完成,例如下載或讀取大文件。 JavaScript 將這些長時間運行的任務卸載到瀏覽器或 Node.js 環境中的不同進程。這樣,它不會阻止所有其他代碼被執行。

通常,異步函數接受一個回調函數,以便在它們完成後我們可以處理它們的數據。

舉個例子,我們寫一個回調函數,當程序成功從我們的硬盤讀取一個文件時,就會執行這個回調函數。

為此,我們將使用一個名為 sample.txt 的文本文件 ,包含以下內容:

Hello world from sample.txt

那我們來寫一個簡單的 Node.js 腳本來讀取文件:

const fs = require('fs');

fs.readFile('./sample.txt', 'utf-8', (err, data) => {
    if (err) {
        // Handle error
        console.error(err);
          return;
    }

    // Data is string do something with it
    console.log(data);
});

for (let i = 0; i < 10; i++) {
    console.log(i);
}

運行這段代碼應該會產生:

0
...
8
9
Hello world from sample.txt

如果您運行此代碼,您應該會看到 0..9 在回調執行之前打印。這是因為我們之前談到的 JavaScript 的異步管理。記錄文件內容的回調,只有在文件被讀取後才會被調用。

附帶說明一下,回調也可以在同步方法中使用。例如,Array.sort() 接受一個回調函數,允許您自定義元素的排序方式。

現在我們對回調有了更好的了解。讓我們繼續看看 Promise 是什麼。

什麼是承諾

ECMAScript 2015 引入了 Promise (俗稱ES6 ) 以改善異步編程的開發人員體驗。顧名思義,這是一個承諾 JavaScript 對象最終會返回一個 value錯誤 .

一個 Promise 有 3 個狀態:

  • 待處理 :表示異步操作未完成的初始狀態。
  • 已完成 :表示異步操作成功完成。
  • 拒絕 :表示異步操作失敗。

大多數承諾最終看起來像這樣:

someAsynchronousFunction()
    .then(data => {
        // After promise is fulfilled
        console.log(data);
    })
    .catch(err => {
        // If promise is rejected
        console.error(err);
    });

Promise 在現代 JavaScript 中很重要,因為它們與 async/await 一起使用 ECMAScript 2016 中引入的關鍵字 .使用 async/await ,我們不需要使用回調或 then()catch() 寫異步代碼。

如果要修改前面的示例,它將如下所示:

try {
    const data = await someAsynchronousFunction();
} catch(err) {
    // If promise is rejected
    console.error(err);
}

這看起來很像“常規”同步 JavaScript!您可以了解更多關於 async/await 在我們的文章中,ES7 中的 Node.js Async Await。

大多數流行的 JavaScript 庫和新項目使用帶有 async/await 的 Promises 關鍵字。

但是,如果您正在更新現有的 repo 或遇到遺留代碼庫,您可能會對將基於回調的 API 移動到基於 Promise 的 API 以改善您的開發體驗感興趣。您的團隊也會感激不盡。

讓我們看幾個將回調轉換為 Promise 的方法!

將回調轉換為承諾

Node.js Promisify

Node.js 中大部分接受回調的異步函數,例如 fs (文件系統)模塊,具有標準的實現方式 - 回調作為最後一個參數傳遞。

例如,這裡是如何使用 fs.readFile() 讀取文件 不指定文本編碼:

fs.readFile('./sample.txt', (err, data) => {
    if (err) {
        console.error(err);
          return;
    }

    // Data is a buffer
    console.log(data);
});

免費電子書:Git Essentials

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

注意 :如果你指定 utf-8 作為編碼,您將獲得字符串輸出。如果你不指定編碼,你會得到一個 Buffer 輸出。

此外,傳遞給函數的回調應該接受 Error 因為它是第一個參數。之後,可以有任意數量的輸出。

如果您需要轉換為 Promise 的函數遵循這些規則,那麼您可以使用 util.promisify,這是一個將回調轉換為 Promise 的原生 Node.js 模塊。

為此,首先導入 util 模塊:

const util = require('util');

然後你使用 promisify 將其轉換為承諾的方法:

const fs = require('fs');
const readFile = util.promisify(fs.readFile);

現在將新創建的函數用作常規承諾:

readFile('./sample.txt', 'utf-8')
    .then(data => {
        console.log(data);
    })
    .catch(err => {
        console.log(err);
    });

或者,您可以使用 async/await 關鍵字如下例所示:

const fs = require('fs');
const util = require('util');

const readFile = util.promisify(fs.readFile);

(async () => {
    try {
        const content = await readFile('./sample.txt', 'utf-8');
        console.log(content);
    } catch (err) {
        console.error(err);
    }
})();

您只能使用 await 使用 async 創建的函數中的關鍵字 ,因此我們在這個例子中有一個函數包裝器。此函數包裝器也稱為立即調用函數表達式。

如果您的回調不遵循該特定標準,請不要擔心。 util.promisify() 函數可以讓您自定義轉換的發生方式。

注意 :Promises 在引入後很快就流行起來。 Node.js 已經將其大部分(如果不是全部)核心函數從回調轉換為基於 Promise 的 API。

如果您需要使用 Promises 處理文件,請使用 Node.js 附帶的庫。

到目前為止,您已經學習瞭如何將 Node.js 標準樣式的回調轉換為 Promise。此模塊僅在 Node.js 版本 8 及以後可用。如果您在瀏覽器或早期版本的 Node 中工作,最好創建自己的基於 Promise 的函數版本。

創建你的承諾

讓我們談談如果 util.promisify() 功能不可用。

這個想法是創建一個新的 Promise 環繞回調函數的對象。如果回調函數返回錯誤,我們會拒絕帶有錯誤的 Promise。如果回調函數返回非錯誤輸出,我們用輸出解析 Promise。

讓我們首先將回調轉換為接受固定數量參數的函數的承諾:

const fs = require('fs');

const readFile = (fileName, encoding) => {
    return new Promise((resolve, reject) => {
        fs.readFile(fileName, encoding, (err, data) => {
            if (err) {
                return reject(err);
            }

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

readFile('./sample.txt')
    .then(data => {
        console.log(data);
    })
    .catch(err => {
        console.log(err);
    });

我們的新函數 readFile() 接受我們用來讀取文件的兩個參數 fs.readFile() .然後我們創建一個新的 Promise 包裹函數的對象,該函數接受回調,在本例中為 fs.readFile() .

我們沒有返回錯誤,而是 reject 承諾。我們不是立即記錄數據,而是 resolve 承諾。然後我們使用基於 Promise 的 readFile() 功能和以前一樣。

讓我們嘗試另一個接受動態數量參數的函數:

const getMaxCustom = (callback, ...args) => {
    let max = -Infinity;

    for (let i of args) {
        if (i > max) {
            max = i;
        }
    }

    callback(max);
}

getMaxCustom((max) => { console.log('Max is ' + max) }, 10, 2, 23, 1, 111, 20);

callback參數也是第一個參數,對於接受回調的函數來說有點不尋常。

轉換為 Promise 的方式相同。我們新建一個Promise 包裹我們使用回調的函數的對象。然後我們 reject 如果我們遇到錯誤和 resolve 當我們得到結果時。

我們承諾的版本如下所示:

const getMaxPromise = (...args) => {
    return new Promise((resolve) => {
        getMaxCustom((max) => {
            resolve(max);
        }, ...args);
    });
}

getMaxCustom(10, 2, 23, 1, 111, 20)
    .then(max => console.log(max));

在創建我們的 Promise 時,函數是否以非標準方式使用回調或帶有許多參數並不重要。我們可以完全控制它是如何完成的,而且原理是一樣的。

結論

雖然回調是在 JavaScript 中利用異步代碼的默認方式,但 Promise 是一種更現代的方法,開發人員認為它更易於使用。如果我們遇到使用回調的代碼庫,我們現在可以將該函數設為 Promise。

在本文中,您首先看到瞭如何使用 utils.promisfy() Node.js 中的方法將接受回調的函數轉換為 Promise。然後您看到瞭如何創建自己的 Promise 包裝一個函數的對象,該函數在不使用外部庫的情況下接受回調。

有了這個,許多遺留的 JavaScript 代碼可以很容易地與更現代的代碼庫和實踐混合在一起!與往常一樣,源代碼在 GitHub 上可用。


Tutorial JavaScript 教程
  1. 使用 Lightning 設計系統為 React 創建組件帶來(更多)樂趣

  2. JavaScript 字符串教程 [使用模板文字創建字符串]

  3. 在 JavaScript 中使用 Break、Continue 和 Return 進行邏輯導航。

  4. 瀏覽器工作原理——HTTP請求與解析

  5. 將樹屋徽章小部件添加到站點

  6. 如何在第二個 HTML 頁面中定義的一個 HTML 頁面編輯元素中觸發 JS 函數?

  7. 探索工廠功能

  1. 陣列方法的心眼

  2. Svelte 中的組件測試

  3. 使用 JavaScript 中對象的值獲取鍵?

  4. 打字稿遷移(JS 到 TS)

  5. React 應該選擇編譯時間而不是虛擬 DOM 嗎?

  6. 為即將到來的項目設置 Nextjs、Apollo 客戶端和 Chakra UI

  7. 你應該了解的 5 個 JavaScript 核心概念

  1. 使用 JWT 身份驗證創建全棧 MERN 應用程序:第 4 部分

  2. RxJS Observables 簡介

  3. 將 Obniz Board 設置為計數器並在 10 分鐘內將數據存儲在 Kintone Web 數據庫中!

  4. 構建批處理通知引擎