摘要:自定義的化有那么一些場景,是不能夠直接使用來進行轉換的,有大概這么兩種情況沒有遵循約定的回調函數返回多個參數的回調函數首先是第一個,如果沒有遵循我們的約定,很可能導致的誤判,得不到正確的反饋。
util.promisify是在node.js 8.x版本中新增的一個工具,用于將老式的Error first callback轉換為Promise對象,讓老項目改造變得更為輕松。
在官方推出這個工具之前,民間已經有很多類似的工具了,比如es6-promisify、thenify、bluebird.promisify。
以及很多其他優秀的工具,都是實現了這樣的功能,幫助我們在處理老項目的時候,不必費神將各種代碼使用Promise再重新實現一遍。
工具實現的大致思路首先要解釋一下這種工具大致的實現思路,因為在Node中異步回調有一個約定:Error first,也就是說回調函數中的第一個參數一定要是Error對象,其余參數才是正確時的數據。
知道了這樣的規律以后,工具就很好實現了,在匹配到第一個參數有值的情況下,觸發reject,其余情況觸發resolve,一個簡單的示例代碼:
function util (func) { return (...arg) => new Promise((resolve, reject) => { func(...arg, (err, arg) => { if (err) reject(err) else resolve(arg) }) }) }
調用工具函數返回一個匿名函數,匿名函數接收原函數的參數。
匿名函數被調用后根據這些參數來調用真實的函數,同時拼接一個用來處理結果的callback。
檢測到err有值,觸發reject,其他情況觸發resolve
resolve 只能傳入一個參數,所以callback中沒有必要使用...arg獲取所有的返回值
常規的使用方式拿一個官方文檔中的示例
const { promisify } = require("util") const fs = require("fs") const statAsync = promisify(fs.stat) statAsync(".").then(stats => { // 拿到了正確的數據 }, err => { // 出現了異常 })
以及因為是Promise,我們可以使用await來進一步簡化代碼:
const { promisify } = require("util") const fs = require("fs") const statAsync = promisify(fs.stat) // 假設在 async 函數中 try { const stats = await statAsync(".") // 拿到正確結果 } catch (e) { // 出現異常 }
用法與其他工具并沒有太大的區別,我們可以很輕易的將回調轉換為Promise,然后應用于新的項目中。
自定義的 Promise 化有那么一些場景,是不能夠直接使用promisify來進行轉換的,有大概這么兩種情況:
沒有遵循Error first callback約定的回調函數
返回多個參數的回調函數
首先是第一個,如果沒有遵循我們的約定,很可能導致reject的誤判,得不到正確的反饋。
而第二項呢,則是因為Promise.resolve只能接收一個參數,多余的參數會被忽略。
所以為了實現正確的結果,我們可能需要手動實現對應的Promise函數,但是自己實現了以后并不能夠確保使用方不會針對你的函數調用promisify。
所以,util.promisify還提供了一個Symbol類型的key,util.promisify.custom。
Symbol類型的大家應該都有了解,是一個唯一的值,這里是util.prosimify用來指定自定義的Promise化的結果的,使用方式如下:
const { promisify } = require("util") // 比如我們有一個對象,提供了一個返回多個參數的回調版本的函數 const obj = { getData (callback) { callback(null, "Niko", 18) // 返回兩個參數,姓名和年齡 } } // 這時使用promisify肯定是不行的 // 因為Promise.resolve只接收一個參數,所以我們只會得到 Niko promisify(obj.getData)().then(console.log) // Niko // 所以我們需要使用 promisify.custom 來自定義處理方式 obj.getData[promisify.custom] = async () => ({ name: "Niko", age: 18 }) // 當然了,這是一個曲線救國的方式,無論如何 Promise 不會返回多個參數過來的 promisify(obj.getData)().then(console.log) // { name: "Niko", age: 18 }
關于Promise為什么不能resolve多個值,我有一個大膽的想法,一個沒有經過考證,強行解釋的理由:如果能resolve多個值,你讓async函數怎么return(當個樂子看這句話就好,不要當真)
不過應該確實跟return有關,因為Promise是可以鏈式調用的,每個Promise中執行then以后都會將其返回值作為一個新的Promise對象resolve的值,在JavaScript中并沒有辦法return多個參數,所以即便第一個Promise可以返回多個參數,只要經過return的處理就會丟失
在使用上就是很簡單的針對可能會被調用promisify的函數上添加promisify.custom對應的處理即可。
當后續代碼調用promisify時就會進行判斷:
如果目標函數存在promisify.custom屬性,則會判斷其類型:
如果不是一個可執行的函數,拋出異常
如果是可執行的函數,則直接返回其對應的函數
如果目標函數不存在對應的屬性,按照Error first callback的約定生成對應的處理函數然后返回
添加了這個custom屬性以后,就不用再擔心使用方針對你的函數調用promisify了。
而且可以驗證,賦值給custom的函數與promisify返回的函數地址是一處:
obj.getData[promisify.custom] = async () => ({ name: "Niko", age: 18 }) // 上邊的賦值為 async 函數也可以改為普通函數,只要保證這個普通函數會返回 Promise 實例即可 // 這兩種方式與上邊的 async 都是完全相等的 obj.getData[promisify.custom] = () => Promise.resolve({ name: "Niko", age: 18 }) obj.getData[promisify.custom] = () => new Promise(resolve({ name: "Niko", age: 18 })) console.log(obj.getData[promisify.custom] === promisify(obj.getData)) // true一些內置的 custom 處理
在一些內置包中,也能夠找到promisify.custom的蹤跡,比如說最常用的child_process.exec就內置了promisify.custom的處理:
const { exec } = require("child_process") const { promisify } = require("util") console.log(typeof exec[promisify.custom]) // function
因為就像前邊示例中所提到的曲線救國的方案,官方的做法也是將函數簽名中的參數名作為key,將其所有參數存放到一個Object對象中進行返回,比如child_process.exec的返回值拋開error以外會包含兩個,stdout和stderr,一個是命令執行后的正確輸出,一個是命令執行后的錯誤輸出:
promisify(exec)("ls").then(console.log) // -> { stdout: "XXX", stderr: "" }
或者我們故意輸入一些錯誤的命令,當然了,這個只能在catch模塊下才能夠捕捉到,一般命令正常執行stderr都會是一個空字符串:
promisify(exec)("lss").then(console.log, console.error) // -> { ..., stdout: "", stderr: "lss: command not found" }
包括像setTimeout、setImmediate也都實現了對應的promisify.custom。
之前為了實現sleep的操作,還手動使用Promise封裝了setTimeout:
const sleep = promisify(setTimeout) console.log(new Date()) await sleep(1000) console.log(new Date())內置的 promisify 轉換后函數
如果你的Node版本使用10.x以上的,還可以從很多內置的模塊中找到類似.promises的子模塊,這里邊包含了該模塊中常用的回調函數的Promise版本(都是async函數),無需再手動進行promisify轉換了。
而且我本人覺得這是一個很好的指引方向,因為之前的工具實現,有的選擇直接覆蓋原有函數,有的則是在原有函數名后邊增加Async進行區分,官方的這種在模塊中多帶帶引入一個子模塊,在里邊實現Promise版本的函數,其實這個在使用上是很方便的,就拿fs模塊進行舉例:
// 之前引入一些 fs 相關的 API 是這樣做的 const { readFile, stat } = require("fs") // 而現在可以很簡單的改為 const { readFile, stat } = require("fs").promises // 或者 const { promises: { readFile, stat } } = require("fs")
后邊要做的就是將調用promisify相關的代碼刪掉即可,對于其他使用API的代碼來講,這個改動是無感知的。
所以如果你的node版本夠高的話,可以在使用內置模塊之前先去翻看文檔,有沒有對應的promises支持,如果有實現的話,就可以直接使用。
一定要符合Error first callback的約定
不能返回多個參數
注意進行轉換的函數是否包含this的引用
前兩個問題,使用前邊提到的promisify.custom都可以解決掉。
但是第三項可能會在某些情況下被我們所忽視,這并不是promisify獨有的問題,就一個很簡單的例子:
const obj = { name: "Niko", getName () { return this.name } } obj.getName() // Niko const func = obj.getName func() // undefined
類似的,如果我們在進行Promise轉換的時候,也是類似這樣的操作,那么可能會導致生成后的函數this指向出現問題。
修復這樣的問題有兩種途徑:
使用箭頭函數,也是推薦的做法
在調用promisify之前使用bind綁定對應的this
不過這樣的問題也是建立在promisify轉換后的函數被賦值給其他變量的情況下會發生。
如果是類似這樣的代碼,那么完全不必擔心this指向的問題:
const obj = { name: "Niko", getName (callback) { callback(null, this.name) } } // 這樣的操作是不需要擔心 this 指向問題的 obj.XXX = promisify(obj.getName) // 如果賦值給了其他變量,那么這里就需要注意 this 的指向了 const func = promisify(obj.getName) // 錯誤的 this小結
個人認為Promise作為當代javaScript異步編程中最核心的一部分,了解如何將老舊代碼轉換為Promise是一件很有意思的事兒。
而我去了解官方的這個工具,原因是在搜索Redis相關的Promise版本時看到了這個readme:
This package is no longer maintained. node_redis now includes support for promises in core, so this is no longer needed.
然后跳到了node_redis里邊的實現方案,里邊提到了util.promisify,遂抓過來研究了一下,感覺還挺有意思,總結了下分享給大家。
參考資料util.promisify
child_process.exec
fs.promises
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98466.html
摘要:而適配器其實在中應該是比較常見的一種了。在維基百科中,關于適配器模式的定義為在軟件工程中,適配器模式是一種軟件設計模式,允許從另一個接口使用現有類的接口。 適配器設計模式在JavaScript中非常有用,在處理跨瀏覽器兼容問題、整合多個第三方SDK的調用,都可以看到它的身影。 其實在日常開發中,很多時候會不經意間寫出符合某種設計模式的代碼,畢竟設計模式就是老前輩們總結提煉出來的一些能...
摘要:例如,的回調函數包含下面幾個參數轉換成之后,它的參數將會變成這樣一個對象通過內部符號處理非標準回調函數。 Nodejs 8 有一個新的工具函數 util.promisify()。他將一個接收回調函數參數的函數轉換成一個返回Promise的函數。 1、util.promisify()小例子 如果你給以下命令傳入文件路徑,則會輸出文件內容 // echo.js const {promis...
摘要:我們就可以升級以前所有的異步回調函數了。大體上來說,這套方案通過使用回調實例包裹原先的回調函數,可以將原先復雜的嵌套展開鋪平,從而降低開發和維護的難度和成本。 Node.js 8 于上個月月底正式發布,帶來了很多新特性。其中比較值得注意的,便有 util.promisify() 這個方法。 如果你已經很熟悉 Promise,請繼續往下看。如果你還不熟悉 Promise,可以先跳過去看下...
摘要:異步編程是每個使用編程的人都會遇到的問題,無論是前端的請求,或是的各種異步。本文就來總結一下常見的四種處理異步編程的方法。利用一種鏈式調用的方法來組織異步代碼,可以將原來以回調函數形式調用的代碼改為鏈式調用。 異步編程是每個使用 JavaScript 編程的人都會遇到的問題,無論是前端的 ajax 請求,或是 node 的各種異步 API。本文就來總結一下常見的四種處理異步編程的方法。...
摘要:一個包括文件緩存傳輸壓縮模版引擎類型匹配等功能的靜態資源服務器,使用的內置模塊實現,可以通過鏈接訪問資源。二使用讀取資源文件我們的目的是搭建一個靜態資源服務器,當訪問一個到資源文件或目錄時,我們希望可以得到它。 一個包括文件緩存、傳輸壓縮、ejs 模版引擎、MIME 類型匹配等功能的 Node 靜態資源服務器,使用 Node 的內置模塊實現,可以通過鏈接訪問資源。 一、創建 HTTP Se...
閱讀 3557·2021-08-02 13:41
閱讀 2390·2019-08-30 15:56
閱讀 1520·2019-08-30 11:17
閱讀 1174·2019-08-29 15:18
閱讀 580·2019-08-29 11:10
閱讀 2671·2019-08-26 13:52
閱讀 508·2019-08-26 13:22
閱讀 2949·2019-08-23 15:41