首頁  >  文章  >  web前端  >  如何將 JavaScript 回呼轉換為 Promise?方法介紹

如何將 JavaScript 回呼轉換為 Promise?方法介紹

青灯夜游
青灯夜游轉載
2020-12-03 17:48:125507瀏覽

如何將 JavaScript 回呼轉換為 Promise?方法介紹

在幾年前,回呼是 JavaScript 中實作執行非同步程式碼的唯一方法。回調本身幾乎沒有什麼問題,最值得注意的是「回調地獄」。

在 ES6 中引入了 Promise 作為這些問題的解決方案。最後透過引入  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) {
        // 处理错误
        console.error(err);
          return;
    }
    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。

什麼是 Promise

在 ECMAScript 2015(ES6)中引入了 Promise,用來改善在非同步程式設計方面的體驗。顧名思義,JavaScript 物件最終將傳回的「值」或「錯誤」應該是一個 Promise。

一個 Promise 有 3 個狀態:

  • Pending(待處理): 用來指示非同步操作尚未完成的初始狀態。
  • Fulfilled(已完成):表示非同步操作已成功完成。
  • Rejected(拒絕):表示非同步操作失敗。

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

someAsynchronousFunction()
    .then(data => {
        // promise 被完成
        console.log(data);
    })
    .catch(err => {
        // promise 被拒绝
        console.error(err);
    });

Promise 在現代JavaScript 中非常重要,因為它們與ECMAScript 2016 中引入的 async/await 關鍵字一起使用。使用 async / await 就不需要再用回呼或 then()catch() 來寫非同步程式碼。

如果要改寫前面的例子,應該是這樣:

try {
    const data = await someAsynchronousFunction();
} catch(err) {
    // promise 被拒绝
    console.error(err);
}

這看起來很像「一般」的同步 JavaScript。大多數流行的JavaScript庫和新專案都把 Promises 與 async/await 關鍵字放在一起使用。

但是,如果你要更新現有的程式庫或遇到舊的程式碼,則可能會對將基於回呼的 API 遷移到基於 Promise 的 API 感興趣,這樣可以改善你的開發體驗。

來看看將回呼轉換為 Promise 的幾種方法。

將回呼轉換為Promise

Node.js Promise

#大多數在Node.js 中接受回呼的非同步函數(例如fs 模組)有標準的實作方式:把回呼當作最後一個參數傳遞。

例如這是在不指定文字編碼的情況下用fs.readFile() 讀取檔案的方法:

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

注意:如果你指定utf-8 作為編碼,那麼得到的輸出就是一個字串。如果不指定得到的輸出是 Buffer

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

如果你需要轉換為 Promise 的函數遵循這些規則,那麼可以用 util.promisify ,這是一個原生 Node.js 模組,其中包含對 Promise 的回呼。

先導入ʻutil`模組:

const util = require('util');

然後用promisify 方法將其轉換為Promise:

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

現在,把新建立的函數用作promise:

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);
    }
})();

你只能在用async 建立的函數中使用await 關鍵字,這也是為什麼要使用函數包裝器的原因。函數包裝器也被稱為立即呼叫的函數表達式。

如果你的回调不遵循这个特定标准也不用担心。 util.promisify() 函数可让你自定义转换是如何发生的。

注意: Promise 在被引入后不久就开始流行了。 Node.js 已经将大部分核心函数从回调转换成了基于 Promise 的API。

如果需要用 Promise 处理文件,可以用 Node.js 附带的库(https://nodejs.org/docs/lates...)。

现在你已经了解了如何将 Node.js 标准样式回调隐含到 Promise 中。从 Node.js 8 开始,这个模块仅在 Node.js 上可用。如果你用的是浏览器或早期版本版本的 Node,则最好创建自己的基于 Promise 的函数版本。

创建你自己的 Promise

让我们讨论一下怎样把回调转为  util.promisify() 函数的 promise。

思路是创建一个新的包含回调函数的 Promise 对象。如果回调函数返回错误,就拒绝带有该错误的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  Promise 而不是返回错误。所以代码中没有立即把数据输出,而是先 resolve 了Promise。然后像以前一样使用基于 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

我们的 promise 版本如下:

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。

在本文中,我们首先学到了如何 在Node.js 中使用 utils.promisfy() 方法将接受回调的函数转换为 Promise。然后,了解了如何创建自己的 Promise 对象,并在对象中包装了无需使用外部库即可接受回调的函数。这样许多旧 JavaScript 代码可以轻松地与现代的代码库和混合在一起。

更多编程相关知识,请访问:编程学习!!

以上是如何將 JavaScript 回呼轉換為 Promise?方法介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除