Эти вещи об util.promisify

Node.js задняя часть Promise Bluebird.js

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)
    })
  })
}
  1. Вызов служебной функции возвращает анонимную функцию, а анонимная функция получает параметры исходной функции.
  2. После того, как анонимная функция называется, реальная функция называется в соответствии с этими параметрами, аcallback.
  3. обнаруженerrзначение, триггерreject, срабатывают другие условия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, а затем примените его к новому проекту.

Пользовательские обещания

Есть некоторые сценарии, которые нельзя использовать напрямуюpromisifyДля конвертации есть примерно два случая:

  1. не последовалError first callbackСогласованная функция обратного вызова
  2. Функция обратного вызова, которая возвращает несколько аргументов

Первое есть первое, и если наши условности не соблюдаются, это, скорее всего, приведет к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Суд будет вынесен, когда:

  1. Если целевая функция существуетpromisify.customсвойство, он определит его тип:
    1. Выдает исключение, если это не исполняемая функция
    2. Если это исполняемая функция, она напрямую возвращает соответствующую функцию.
  2. Если в целевой функции нет соответствующего свойства, следуйте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

некоторая встроенная пользовательская обработка

В некоторых встроенных пакетах вы также можете найти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())

Встроенная функция посттрансформации обещаний

если ваш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Поддержка, если она реализована, может быть использована напрямую.

Некоторые заметки о обещаниях

  1. должен встретитьError first callbackконвенция
  2. не может возвращать несколько параметров
  3. Обратите внимание, что функция, выполняющая преобразование, содержитthisцитаты

Для первых двух вопросов используйте ранее упомянутыйpromisify.customможно решить.
А вот третий пункт может быть нами в некоторых случаях проигнорирован, это неpromisifyУникальная проблема, просто очень простой пример:

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

obj.getName() // Niko

const func = obj.getName

func() // undefined

Аналогично, если мы делаемPromiseПри преобразовании он также похож на это, поэтому он может привести к сгенерированной функции.thisУкажите на проблему.
Есть два способа исправить такую ​​проблему:

  1. Также рекомендуется использовать стрелочные функции.
  2. вызов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, я схватил его и изучил, он показался мне довольно интересным, я обобщил его и поделился с вами.

использованная литература