你不知道的 Node.js util
從類型判斷說(shuō)起
在 JavaScript 中,進(jìn)行變量的類型校驗(yàn)是一個(gè)非常令人頭疼的事,如果只是簡(jiǎn)單的使用 typeof 會(huì)到各種各樣的問(wèn)題。
舉幾個(gè)簡(jiǎn)單的🌰:
- console.log(typeof null) // 'object'
- console.log(typeof new Array) // 'object'
- console.log(typeof new String) // 'object'
后來(lái),大家發(fā)現(xiàn)可以使用 Object.prototype.toString() 方法來(lái)進(jìn)行變量類型的判斷。
- const getTypeString = obj => Object.prototype.toString.call(obj)
- getTypeString(null) // '[object Null]'
- getTypeString('string') //'[object String]'
- getTypeString(new String) //'[object String]'
對(duì) toString() 方法進(jìn)行代理,可以得到一個(gè)類型字符串,我們就可以在這個(gè)字符串上面搞事情。
- const getTypeString = obj => {
- return Object.prototype.toString.call(obj)
- }
- const isType = type => {
- return obj => {
- return getTypeString(obj) === `[object ${type}]`
- }
- }
- const isArray = isType('Array') // 該方法一般通過(guò) Array.isArray 代替
- const isNull = isType('Null')
- const isObject = isType('Object')
- const isRegExp = isType('RegExp')
- const isFunction = isType('Function')
- const isAsyncFunction = isType('AsyncFunction')
- isNull(null) // true
- isObject({}) // true
- isRegExp(/\w/) // true
- isFunction(() => {}) // true
- isAsyncFunction(async () => {}) // true
But,在 Node.js 中,內(nèi)部其實(shí)是有一組用來(lái)判斷變量類型的 api 的。而且功能異常豐富,除了基礎(chǔ)類型的判斷,還支持判斷 Promise 對(duì)象、Date 對(duì)象、各種ArrayBuffer。
- const types = require('util/types')
- types.isDate(new Date) // true
- types.isPromise(new Promise(() => {})) // true
- types.isArrayBuffer(new ArrayBuffer(16)) // true
嚴(yán)格相等
在 JavaScript 中,對(duì)象、數(shù)組等變量在判斷相等的過(guò)程中,如果用 === 通常只會(huì)判斷這兩個(gè)變量是否指向同一內(nèi)存地址。如果想判斷對(duì)象的鍵對(duì)應(yīng)的所有值是否相等,需要對(duì)兩個(gè)對(duì)象進(jìn)行遍歷。在 util 中,也提供了一個(gè)方法可以用來(lái)判斷兩個(gè)對(duì)象是否嚴(yán)格相等:util.isDeepStrictEqual(val1, val2)
- const util = require('util')
- const val1 = { name: 'shenfq' }
- const val2 = { name: 'shenfq' }
- console.log('val1 === val2', val1 === val2) // false
- console.log('isDeepStrictEqual', util.isDeepStrictEqual(val1, val2)) // true
該方法同樣可以用來(lái)判斷數(shù)組,是否嚴(yán)格相等:
- const util = require('util')
- const arr1 = [1, 3, 5]
- const arr2 = [1, 3, 5]
- console.log('arr1 === arr2', arr1 === arr2) // false
- console.log('isDeepStrictEqual', util.isDeepStrictEqual(arr1, arr2)) // true
Error First & Promise
早期的 Node API 都是 Error First 風(fēng)格的,也就是所有的異步函數(shù)都會(huì)接受一個(gè)回調(diào)函數(shù),該回調(diào)的一個(gè)參數(shù)為 error 對(duì)象,如果正常返回 error 對(duì)象為 null,后面的參數(shù)為成功響應(yīng)的結(jié)果。
- // 下面是一個(gè)讀取文件的示例
- const fs = require('fs')
- fs.readFile('nginx.log', (error, data) => {
- if (error) {
- // 讀取文件失敗
- console.error(error)
- return
- }
- // 讀取文件成功,打印結(jié)果
- console.log(data)
- })
在 Node 8 發(fā)布的時(shí)候,新增了一個(gè) promisify 接口,用于將 Error First 風(fēng)格的 API 轉(zhuǎn)為 Promise API。
- const fs = require('fs')
- const util = require('util')
- const readFile = util.promisify(fs.readFile)
- readFile('./2021-11-11.log', { encoding: 'utf-8' })
- .then(text => console.log(text))
- .catch(error => console.error(error))
不過(guò),后來(lái)也有很多人覺(jué)得這些原生 API 支持 Promise 的方式太過(guò)繁瑣,每個(gè) API 都需要單獨(dú)的包裝一層 promisify 方法。在 Node 10 發(fā)布的時(shí)候,原生模塊都新增了一個(gè) .promises 屬性,該屬性下的所有 API 都 Promise 風(fēng)格的。
- const fs = require('fs').promises
- fs.readFile('./2021-11-11.log', { encoding: 'utf-8' })
- .then(text => console.log(text))
- .catch(error => console.error(error))
注意:Node 14 后,promises API 又新增了一種引入方式,通過(guò)修改包名的方式引入。
- const fs = require('fs/promises')
- fs.readFile('./2021-11-11.log', { encoding: 'utf-8' })
- .then(text => console.log(text))
- .catch(error => console.error(error))
除了將 Error First 風(fēng)格的 API 轉(zhuǎn)為 Promise API,util 中還提供 callbackify 方法,用于將 async 函數(shù)轉(zhuǎn)換為 Error First 風(fēng)格的函數(shù)。
下面通過(guò) callbackify 將 promise 化的 fs 還原為 Error First 風(fēng)格的函數(shù)。
- const fs = require('fs/promises')
- const util = require('util')
- const readFile = util.callbackify(fs.readFile)
- readFile('./2021-11-12.log', { encoding: 'utf-8' }, (error, text) => {
- if (error) {
- console.error(error)
- return
- }
- console.log(text)
- })
調(diào)試與輸出
如果有開(kāi)發(fā)過(guò) Node 服務(wù),應(yīng)該都用過(guò) debug 模塊,通過(guò)該模塊可以在控制臺(tái)看到更加明晰的調(diào)試信息。
- const debug = require('debug')
- const log = debug('app')
- const user = { name: 'shenfq' }
- log('當(dāng)前用戶: %o', user)
其實(shí),通過(guò) util.debug 也能實(shí)現(xiàn)類似的效果:
- const debug = require('debug')
- const log = debug('app')
- const user = { name: 'shenfq' }
- log('當(dāng)前用戶: %o', user)
只是在啟動(dòng)時(shí),需要將 DEBUG 環(huán)境變量替換為 NODE_DEBUG。
如果你有認(rèn)真看上面的代碼,應(yīng)該會(huì)發(fā)現(xiàn),在 log('當(dāng)前用戶: %o', user) 方法前面的字符串中,有一個(gè) %o 占位符,表示這個(gè)地方將會(huì)填充一個(gè)對(duì)象(object)。這與 C 語(yǔ)言或 python 中的,printf 類似。同樣,在 util 模塊中,直接提供了格式化的方法:util.format。
- const { format } = require('util')
- console.log(
- format('當(dāng)前用戶: %o', {
- name: 'shenfq', age: 25
- })
- )
除了 %o 占位符,不同的數(shù)據(jù)類型應(yīng)使用不同的占位符。
JavaScript 中的對(duì)象是一個(gè)很復(fù)雜的東西,除了直接使用 util.format 外加 %o 占位符的方式格式化對(duì)象,util 中還提供了一個(gè)叫做 inspect 方法來(lái)進(jìn)行對(duì)象格式化。
- const { inspect } = require('util')
- const user = {
- age: 25,
- name: 'shenfq',
- work: {
- name: 'coding',
- seniority: 5
- }
- }
- console.log(inspect(user))
這么看 inspect 好像什么都沒(méi)做,但是 inspect 方法還有第二個(gè)參數(shù),用來(lái)進(jìn)行格式化時(shí)的一些個(gè)性化配置。
- depth: number:控制顯示層級(jí);
- sorted: boolean|Function: 是否按照key的編碼值進(jìn)行排序;
- compact: boolean:是否進(jìn)行單行顯示;
當(dāng)然上面只是一部分配置,更詳細(xì)的配置可查閱 node 文檔,下面我們寫幾個(gè)案例:
所有的屬性都換行顯示:
- inspect(user, {
- compact: false
- })
只格式化對(duì)象第一層的值:
- inspect(user, {
- depth: 0,
- compact: false
- })
按照key值的編碼倒序輸出:
- inspect(user, {
- compact: false,
- sorted: (a, b) => a < b ? 1 : -1
- })