Новые шаблоны, включенные асинхронными функциями.

Чем больше я живу с async / await, тем больше я открываю новые шаблоны и оставляю после себя множество старых.

Одним из ограничений работы с async / await является то, что мы имеем только одно возвращаемое значение из асинхронных функций. В старом мире обратного вызова вы могли передать обратному вызову более одного объекта успеха.

В дополнение к нескольким результатам обратного вызова у вас также была возможность вернуть поток из вашей функции (при этом все еще принимая обратный вызов), что позволило вам создавать гибридные API, которые принимали ввод / вывод Steam в дополнение к параметрам функции и обратным вызовам.

Я часто использовал это в запросе. Целью запроса всегда было уменьшить количество набора текста, который вам нужно было сделать, чтобы использовать HTTP. Но даже несмотря на то, насколько гибким стал этот API, был общий шаблон, который можно найти почти везде, где люди используют запрос.

request(url, {json: true}, (err, resp, body) => {
  if (err) return cb(err)
  if (resp.statusCode !== 200) return cb(new JSONError(body))
  // success!
  console.log(body)
})

На практике я обычно пишу небольшие функции поверх запроса для обработки этого шаблона. Но когда я начал воспринимать async / await как должное, эти ограничения начали давить на меня все больше и больше. В конце концов я начал думать об альтернативном подходе.

Ограничения вместо функций.

запрос реализует множество функций, которые вы включаете с помощью различных параметров ввода. При включении некоторые из этих функций также накладывают ограничения (включение декодирования JSON означает, что ответы, не относящиеся к JSON, не будут выполнены), но эти функции настолько универсальны, что ограничения не могут быть расширены до того, что вы обычно хотите (включение декодирования JSON ДЕЙСТВУЕТ НЕ требует, чтобы код статуса был 200).

Когда я писал запрос, я уже много времени провел с HTTP. Я много работал с Python и в конечном итоге работал над Node.js Core HTTP, добавляя функции, которые также повлияли на запрос. request всегда был HTTP-клиентом с точки зрения того, кто знает HTTP. Но после 8 лет использования запросов я гораздо лучше понимаю, какой шаблон лучше для повседневного использования.

Одно большое изменение в моем мышлении состоит в том, что клиент не должен предлагать функции, он должен предлагать ограничения. Я хочу сказать клиенту, что приемлемо, и построить единственный узкий путь к успеху, который клиент может навязать без какого-либо другого кода с моей стороны.

Это хорошо согласуется с ограничениями async / await. Чем больше мы ограничиваем путь успеха, тем проще получить одно возвращаемое значение и тем ближе это возвращаемое значение к тому, что хочет видеть пользователь.

Представляем загнутый

let request = bent()
let response = await request(url)

Это похоже на ничто, но на самом деле это уже сильно ограничено настройками Bent по умолчанию. Метод - GET, код состояния должен быть 200, возвращаемое значение - объект ответа (который также является потоком). Но давайте сделаем еще немного.

let api = bent('https://api.site.com/v1', 'json')
let obj = await api('/entry/point.json')

Я удивлен, что никогда не видел этого в HTTP-клиентах, учитывая, насколько часто клиент работает только с одной службой API в данном файле кода. С помощью bent мы создаем асинхронную функцию, подготовленную только для этой службы API, которая представляет собой службу JSON по базовому URL-адресу. Возвращаемые значения теперь декодируются в формате JSON, любой другой ответ вызовет ошибку.

Возможно, в информатике уже есть причудливый термин для обозначения «функции, которая возвращает асинхронную функцию», но меня интересует не эта часть этого шаблона. Меня гораздо больше интересует переход к API, которые спрашивают «что разрешено», а не просто «что вы хотите добавить».

let put = bent('PUT', 'https://api.site.com/v1', 201)
await put('/upload', Buffer.from('example'))

Эта функция также принимает поток.

await put('/upload', fs.createReadStream('./file.txt'))

Еще одна интересная особенность async / await заключается в том, что мы можем нормализовать входные значения (буфер, JSON или поток). С обратными вызовами у вас был стимул использовать интерфейс конвейера при работе с потоками, потому что он часто мог избавить вас от необходимости также обратного вызова. С async / await такого преимущества нет. Фактически, вы хотите, чтобы возвращаемое значение оставалось одинаковым для разных типов ввода, чтобы поток стал просто еще одним параметром.

P.S. Как и все модули, которые я пишу сейчас, есть 100% покрытие кода и автоматические выпуски с семантическим выпуском. Хотя этот модуль новый, на самом деле он довольно прочный и в хорошем состоянии 😀