成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

util.promisify 的那些事兒

shuibo / 3350人閱讀

摘要:自定義的化有那么一些場(chǎng)景,是不能夠直接使用來(lái)進(jìn)行轉(zhuǎn)換的,有大概這么兩種情況沒(méi)有遵循約定的回調(diào)函數(shù)返回多個(gè)參數(shù)的回調(diào)函數(shù)首先是第一個(gè),如果沒(méi)有遵循我們的約定,很可能導(dǎo)致的誤判,得不到正確的反饋。

util.promisify是在node.js 8.x版本中新增的一個(gè)工具,用于將老式的Error first callback轉(zhuǎn)換為Promise對(duì)象,讓老項(xiàng)目改造變得更為輕松。

在官方推出這個(gè)工具之前,民間已經(jīng)有很多類(lèi)似的工具了,比如es6-promisify、thenify、bluebird.promisify。

以及很多其他優(yōu)秀的工具,都是實(shí)現(xiàn)了這樣的功能,幫助我們?cè)谔幚砝享?xiàng)目的時(shí)候,不必費(fèi)神將各種代碼使用Promise再重新實(shí)現(xiàn)一遍。

工具實(shí)現(xiàn)的大致思路

首先要解釋一下這種工具大致的實(shí)現(xiàn)思路,因?yàn)樵?b>Node中異步回調(diào)有一個(gè)約定:Error first,也就是說(shuō)回調(diào)函數(shù)中的第一個(gè)參數(shù)一定要是Error對(duì)象,其余參數(shù)才是正確時(shí)的數(shù)據(jù)。

知道了這樣的規(guī)律以后,工具就很好實(shí)現(xiàn)了,在匹配到第一個(gè)參數(shù)有值的情況下,觸發(fā)reject,其余情況觸發(fā)resolve,一個(gè)簡(jiǎn)單的示例代碼:

function util (func) {
  return (...arg) => new Promise((resolve, reject) => {
    func(...arg, (err, arg) => {
      if (err) reject(err)
      else resolve(arg)
    })
  })
}

調(diào)用工具函數(shù)返回一個(gè)匿名函數(shù),匿名函數(shù)接收原函數(shù)的參數(shù)。

匿名函數(shù)被調(diào)用后根據(jù)這些參數(shù)來(lái)調(diào)用真實(shí)的函數(shù),同時(shí)拼接一個(gè)用來(lái)處理結(jié)果的callback。

檢測(cè)到err有值,觸發(fā)reject,其他情況觸發(fā)resolve

resolve 只能傳入一個(gè)參數(shù),所以callback中沒(méi)有必要使用...arg獲取所有的返回值

常規(guī)的使用方式
拿一個(gè)官方文檔中的示例
const { promisify } = require("util")
const fs = require("fs")

const statAsync = promisify(fs.stat)

statAsync(".").then(stats => {
  // 拿到了正確的數(shù)據(jù)
}, err => {
  // 出現(xiàn)了異常
})

以及因?yàn)槭?b>Promise,我們可以使用await來(lái)進(jìn)一步簡(jiǎn)化代碼:

const { promisify } = require("util")
const fs = require("fs")

const statAsync = promisify(fs.stat)

// 假設(shè)在 async 函數(shù)中
try {
  const stats = await statAsync(".")
  // 拿到正確結(jié)果
} catch (e) {
  // 出現(xiàn)異常
}

用法與其他工具并沒(méi)有太大的區(qū)別,我們可以很輕易的將回調(diào)轉(zhuǎn)換為Promise,然后應(yīng)用于新的項(xiàng)目中。

自定義的 Promise 化

有那么一些場(chǎng)景,是不能夠直接使用promisify來(lái)進(jìn)行轉(zhuǎn)換的,有大概這么兩種情況:

沒(méi)有遵循Error first callback約定的回調(diào)函數(shù)

返回多個(gè)參數(shù)的回調(diào)函數(shù)

首先是第一個(gè),如果沒(méi)有遵循我們的約定,很可能導(dǎo)致reject的誤判,得不到正確的反饋。
而第二項(xiàng)呢,則是因?yàn)?b>Promise.resolve只能接收一個(gè)參數(shù),多余的參數(shù)會(huì)被忽略。

所以為了實(shí)現(xiàn)正確的結(jié)果,我們可能需要手動(dòng)實(shí)現(xiàn)對(duì)應(yīng)的Promise函數(shù),但是自己實(shí)現(xiàn)了以后并不能夠確保使用方不會(huì)針對(duì)你的函數(shù)調(diào)用promisify。

所以,util.promisify還提供了一個(gè)Symbol類(lèi)型的keyutil.promisify.custom。

Symbol類(lèi)型的大家應(yīng)該都有了解,是一個(gè)唯一的值,這里是util.prosimify用來(lái)指定自定義的Promise化的結(jié)果的,使用方式如下:

const { promisify } = require("util")
// 比如我們有一個(gè)對(duì)象,提供了一個(gè)返回多個(gè)參數(shù)的回調(diào)版本的函數(shù)
const obj = {
  getData (callback) {
    callback(null, "Niko", 18) // 返回兩個(gè)參數(shù),姓名和年齡
  }
}

// 這時(shí)使用promisify肯定是不行的
// 因?yàn)镻romise.resolve只接收一個(gè)參數(shù),所以我們只會(huì)得到 Niko

promisify(obj.getData)().then(console.log) // Niko

// 所以我們需要使用 promisify.custom 來(lái)自定義處理方式

obj.getData[promisify.custom] = async () => ({ name: "Niko", age: 18 })

// 當(dāng)然了,這是一個(gè)曲線(xiàn)救國(guó)的方式,無(wú)論如何 Promise 不會(huì)返回多個(gè)參數(shù)過(guò)來(lái)的
promisify(obj.getData)().then(console.log) // { name: "Niko", age: 18 }

關(guān)于Promise為什么不能resolve多個(gè)值,我有一個(gè)大膽的想法,一個(gè)沒(méi)有經(jīng)過(guò)考證,強(qiáng)行解釋的理由:如果能resolve多個(gè)值,你讓async函數(shù)怎么return(當(dāng)個(gè)樂(lè)子看這句話(huà)就好,不要當(dāng)真)
不過(guò)應(yīng)該確實(shí)跟return有關(guān),因?yàn)?b>Promise是可以鏈?zhǔn)秸{(diào)用的,每個(gè)Promise中執(zhí)行then以后都會(huì)將其返回值作為一個(gè)新的Promise對(duì)象resolve的值,在JavaScript中并沒(méi)有辦法return多個(gè)參數(shù),所以即便第一個(gè)Promise可以返回多個(gè)參數(shù),只要經(jīng)過(guò)return的處理就會(huì)丟失

在使用上就是很簡(jiǎn)單的針對(duì)可能會(huì)被調(diào)用promisify的函數(shù)上添加promisify.custom對(duì)應(yīng)的處理即可。
當(dāng)后續(xù)代碼調(diào)用promisify時(shí)就會(huì)進(jìn)行判斷:

如果目標(biāo)函數(shù)存在promisify.custom屬性,則會(huì)判斷其類(lèi)型:

如果不是一個(gè)可執(zhí)行的函數(shù),拋出異常

如果是可執(zhí)行的函數(shù),則直接返回其對(duì)應(yīng)的函數(shù)

如果目標(biāo)函數(shù)不存在對(duì)應(yīng)的屬性,按照Error first callback的約定生成對(duì)應(yīng)的處理函數(shù)然后返回

添加了這個(gè)custom屬性以后,就不用再擔(dān)心使用方針對(duì)你的函數(shù)調(diào)用promisify了。
而且可以驗(yàn)證,賦值給custom的函數(shù)與promisify返回的函數(shù)地址是一處:

obj.getData[promisify.custom] = async () => ({ name: "Niko", age: 18 })

// 上邊的賦值為 async 函數(shù)也可以改為普通函數(shù),只要保證這個(gè)普通函數(shù)會(huì)返回 Promise 實(shí)例即可
// 這兩種方式與上邊的 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
一些內(nèi)置的 custom 處理

在一些內(nèi)置包中,也能夠找到promisify.custom的蹤跡,比如說(shuō)最常用的child_process.exec就內(nèi)置了promisify.custom的處理:

const { exec } = require("child_process")
const { promisify } = require("util")

console.log(typeof exec[promisify.custom]) // function

因?yàn)榫拖袂斑吺纠兴岬降那€(xiàn)救國(guó)的方案,官方的做法也是將函數(shù)簽名中的參數(shù)名作為key,將其所有參數(shù)存放到一個(gè)Object對(duì)象中進(jìn)行返回,比如child_process.exec的返回值拋開(kāi)error以外會(huì)包含兩個(gè),stdoutstderr,一個(gè)是命令執(zhí)行后的正確輸出,一個(gè)是命令執(zhí)行后的錯(cuò)誤輸出:

promisify(exec)("ls").then(console.log)
// -> { stdout: "XXX", stderr: "" }

或者我們故意輸入一些錯(cuò)誤的命令,當(dāng)然了,這個(gè)只能在catch模塊下才能夠捕捉到,一般命令正常執(zhí)行stderr都會(huì)是一個(gè)空字符串:

promisify(exec)("lss").then(console.log, console.error)
// -> { ..., stdout: "", stderr: "lss: command not found" }

包括像setTimeoutsetImmediate也都實(shí)現(xiàn)了對(duì)應(yīng)的promisify.custom。
之前為了實(shí)現(xiàn)sleep的操作,還手動(dòng)使用Promise封裝了setTimeout

const sleep = promisify(setTimeout)

console.log(new Date())

await sleep(1000)

console.log(new Date())
內(nèi)置的 promisify 轉(zhuǎn)換后函數(shù)

如果你的Node版本使用10.x以上的,還可以從很多內(nèi)置的模塊中找到類(lèi)似.promises的子模塊,這里邊包含了該模塊中常用的回調(diào)函數(shù)的Promise版本(都是async函數(shù)),無(wú)需再手動(dòng)進(jìn)行promisify轉(zhuǎn)換了。

而且我本人覺(jué)得這是一個(gè)很好的指引方向,因?yàn)橹暗墓ぞ邔?shí)現(xiàn),有的選擇直接覆蓋原有函數(shù),有的則是在原有函數(shù)名后邊增加Async進(jìn)行區(qū)分,官方的這種在模塊中多帶帶引入一個(gè)子模塊,在里邊實(shí)現(xiàn)Promise版本的函數(shù),其實(shí)這個(gè)在使用上是很方便的,就拿fs模塊進(jìn)行舉例:

// 之前引入一些 fs 相關(guān)的 API 是這樣做的
const { readFile, stat } = require("fs")

// 而現(xiàn)在可以很簡(jiǎn)單的改為
const { readFile, stat } = require("fs").promises
// 或者
const { promises: { readFile, stat } } = require("fs")

后邊要做的就是將調(diào)用promisify相關(guān)的代碼刪掉即可,對(duì)于其他使用API的代碼來(lái)講,這個(gè)改動(dòng)是無(wú)感知的。
所以如果你的node版本夠高的話(huà),可以在使用內(nèi)置模塊之前先去翻看文檔,有沒(méi)有對(duì)應(yīng)的promises支持,如果有實(shí)現(xiàn)的話(huà),就可以直接使用。

promisify 的一些注意事項(xiàng)

一定要符合Error first callback的約定

不能返回多個(gè)參數(shù)

注意進(jìn)行轉(zhuǎn)換的函數(shù)是否包含this的引用

前兩個(gè)問(wèn)題,使用前邊提到的promisify.custom都可以解決掉。
但是第三項(xiàng)可能會(huì)在某些情況下被我們所忽視,這并不是promisify獨(dú)有的問(wèn)題,就一個(gè)很簡(jiǎn)單的例子:

const obj = {
  name: "Niko",
  getName () {
    return this.name
  }
}

obj.getName() // Niko

const func = obj.getName

func() // undefined

類(lèi)似的,如果我們?cè)谶M(jìn)行Promise轉(zhuǎn)換的時(shí)候,也是類(lèi)似這樣的操作,那么可能會(huì)導(dǎo)致生成后的函數(shù)this指向出現(xiàn)問(wèn)題。
修復(fù)這樣的問(wèn)題有兩種途徑:

使用箭頭函數(shù),也是推薦的做法

在調(diào)用promisify之前使用bind綁定對(duì)應(yīng)的this

不過(guò)這樣的問(wèn)題也是建立在promisify轉(zhuǎn)換后的函數(shù)被賦值給其他變量的情況下會(huì)發(fā)生。
如果是類(lèi)似這樣的代碼,那么完全不必?fù)?dān)心this指向的問(wèn)題:

const obj = {
  name: "Niko",
  getName (callback) {
    callback(null, this.name)
  }
}

// 這樣的操作是不需要擔(dān)心 this 指向問(wèn)題的
obj.XXX = promisify(obj.getName)

// 如果賦值給了其他變量,那么這里就需要注意 this 的指向了
const func = promisify(obj.getName) // 錯(cuò)誤的 this
小結(jié)

個(gè)人認(rèn)為Promise作為當(dāng)代javaScript異步編程中最核心的一部分,了解如何將老舊代碼轉(zhuǎn)換為Promise是一件很有意思的事兒。
而我去了解官方的這個(gè)工具,原因是在搜索Redis相關(guān)的Promise版本時(shí)看到了這個(gè)readme:

This package is no longer maintained. node_redis now includes support for promises in core, so this is no longer needed.

然后跳到了node_redis里邊的實(shí)現(xiàn)方案,里邊提到了util.promisify,遂抓過(guò)來(lái)研究了一下,感覺(jué)還挺有意思,總結(jié)了下分享給大家。

參考資料

util.promisify

child_process.exec

fs.promises

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/98466.html

相關(guān)文章

  • 適配器在JavaScript中體現(xiàn)

    摘要:而適配器其實(shí)在中應(yīng)該是比較常見(jiàn)的一種了。在維基百科中,關(guān)于適配器模式的定義為在軟件工程中,適配器模式是一種軟件設(shè)計(jì)模式,允許從另一個(gè)接口使用現(xiàn)有類(lèi)的接口。 適配器設(shè)計(jì)模式在JavaScript中非常有用,在處理跨瀏覽器兼容問(wèn)題、整合多個(gè)第三方SDK的調(diào)用,都可以看到它的身影。 其實(shí)在日常開(kāi)發(fā)中,很多時(shí)候會(huì)不經(jīng)意間寫(xiě)出符合某種設(shè)計(jì)模式的代碼,畢竟設(shè)計(jì)模式就是老前輩們總結(jié)提煉出來(lái)的一些能...

    z2xy 評(píng)論0 收藏0
  • [譯] Node.js 8: util.promisify()

    摘要:例如,的回調(diào)函數(shù)包含下面幾個(gè)參數(shù)轉(zhuǎn)換成之后,它的參數(shù)將會(huì)變成這樣一個(gè)對(duì)象通過(guò)內(nèi)部符號(hào)處理非標(biāo)準(zhǔn)回調(diào)函數(shù)。 Nodejs 8 有一個(gè)新的工具函數(shù) util.promisify()。他將一個(gè)接收回調(diào)函數(shù)參數(shù)的函數(shù)轉(zhuǎn)換成一個(gè)返回Promise的函數(shù)。 1、util.promisify()小例子 如果你給以下命令傳入文件路徑,則會(huì)輸出文件內(nèi)容 // echo.js const {promis...

    Shimmer 評(píng)論0 收藏0
  • Node.js 8 中 `util.promisify`

    摘要:我們就可以升級(jí)以前所有的異步回調(diào)函數(shù)了。大體上來(lái)說(shuō),這套方案通過(guò)使用回調(diào)實(shí)例包裹原先的回調(diào)函數(shù),可以將原先復(fù)雜的嵌套展開(kāi)鋪平,從而降低開(kāi)發(fā)和維護(hù)的難度和成本。 Node.js 8 于上個(gè)月月底正式發(fā)布,帶來(lái)了很多新特性。其中比較值得注意的,便有 util.promisify() 這個(gè)方法。 如果你已經(jīng)很熟悉 Promise,請(qǐng)繼續(xù)往下看。如果你還不熟悉 Promise,可以先跳過(guò)去看下...

    HackerShell 評(píng)論0 收藏0
  • JavaScript 異步編程四種方式

    摘要:異步編程是每個(gè)使用編程的人都會(huì)遇到的問(wèn)題,無(wú)論是前端的請(qǐng)求,或是的各種異步。本文就來(lái)總結(jié)一下常見(jiàn)的四種處理異步編程的方法。利用一種鏈?zhǔn)秸{(diào)用的方法來(lái)組織異步代碼,可以將原來(lái)以回調(diào)函數(shù)形式調(diào)用的代碼改為鏈?zhǔn)秸{(diào)用。 異步編程是每個(gè)使用 JavaScript 編程的人都會(huì)遇到的問(wèn)題,無(wú)論是前端的 ajax 請(qǐng)求,或是 node 的各種異步 API。本文就來(lái)總結(jié)一下常見(jiàn)的四種處理異步編程的方法。...

    microelec 評(píng)論0 收藏0
  • 【Node】搭建一個(gè)靜態(tài)資源服務(wù)器

    摘要:一個(gè)包括文件緩存?zhèn)鬏攭嚎s模版引擎類(lèi)型匹配等功能的靜態(tài)資源服務(wù)器,使用的內(nèi)置模塊實(shí)現(xiàn),可以通過(guò)鏈接訪(fǎng)問(wèn)資源。二使用讀取資源文件我們的目的是搭建一個(gè)靜態(tài)資源服務(wù)器,當(dāng)訪(fǎng)問(wèn)一個(gè)到資源文件或目錄時(shí),我們希望可以得到它。 一個(gè)包括文件緩存、傳輸壓縮、ejs 模版引擎、MIME 類(lèi)型匹配等功能的 Node 靜態(tài)資源服務(wù)器,使用 Node 的內(nèi)置模塊實(shí)現(xiàn),可以通過(guò)鏈接訪(fǎng)問(wèn)資源。 一、創(chuàng)建 HTTP Se...

    Mr_houzi 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<