Асинхронные функции в TypeScript являются «заразными» в некотором смысле: как только вы вводите асинхронную секцию в конвейер, вы почти всегда должны делать весь конвейер асинхронным. Это не так уж и плохо, поскольку есть несколько хороших инструментов для работы с асинхронными функциями.
Предположим, вы хотите sh изменить свою функцию loadaddons
на асинхронную. Первое, что нужно учитывать, это подпись типа. Вместо того, чтобы возвращать значение типа Array<addon>
, теперь он будет возвращать значение типа Promise<Array<addon>>
. Итак, ваша новая подпись выглядит следующим образом:
function loadaddons(): Promise<Array<addon>> {
// ... to be updated
}
Теперь нам нужно изменить тело этой функции, чтобы она выполнялась асинхронно. Вы прекрасно написали свою функцию как «конвейер» действий ( read , затем filter , затем act , et c.), Что делает это особенно просто для преобразования. Рассмотрите возможность изменения конвейера по одной секции за раз.
Сначала нам нужно заменить readdirSync
на readdir
. Принимая во внимание, что readdirSync
возвращает Array<string>
, readdir
ожидает функцию в качестве последнего аргумента; она вызывает эту функцию с массивом имен файлов, как только они будут готовы. Я предпочел бы работать с обещаниями, а не с обратными вызовами, и, к счастью, достаточно просто преобразовать функцию, которая ожидает обратный вызов, в функцию, которая возвращает обещание. Все, что требуется, это функция promisify
от модуля 'util'
. То есть promisify(readdir)(path.join(__dirname, 'addons'))
возвращает обещание по желанию.
Теперь мы не можем просто вызвать метод filter
непосредственно для значения, возвращаемого promisify(readdir)
, так как это обещание, а не "обычное" " ценность. Практически все, что мы можем сделать с Promise
, это предоставить функцию , которая будет вызываться со значением, которое обещание разрешает до . Это включает развлечение гипотетического «предположения, что это обещание разрешается в массив имен файлов с именем fs
; что бы я хотел сделать с этим массивом?». Вот как мы на это отвечаем:
import { promisify } from 'util';
promisify(readdir)(path.join(__dirname, 'addons'))
.then(fs => fs.filter(f => f.endsWith('.js')))
Теперь мы кое-что получаем. Это выражение Promise
, которое преобразуется в массив, содержащий имена всех файлов JavaScript в каталоге. Все, что осталось сделать, это import
этих файлов и собрать некоторую информацию из них.
Давайте представим, что нужно сделать для одного файла. Скажите, что его имя файла 'my-file.js'
. Мы хотим import
это, а затем создать объект, который удовлетворяет интерфейсу addon
. Так как import
создает Promise
, мы предоставим функцию, которая отвечает гипотетическому «предположим, что это обещание разрешается в файл JavaScript с именем contents
; как мне извлечь из него нужные мне данные?». Это может выглядеть так:
import('my-file.js').then(contents => ({
name: contents.addonInfo.name,
desc: contents.addonInfo.desc,
run: contents.startAddon
}));
Обратите внимание, что это выражение Promise<addon>
. Мы хотим взять Array из них и получить Promise<Array<addon>>
. Оказывается, для такой вещи уже есть изящная функция! Он называется Promise.all
, и он ожидает значение типа Array<Promise<T>>
и выдает Promise<Array<T>>
.
Вот оно в действии:
import { promisify } from 'util';
promisify(readdir)(path.join(__dirname, 'addons'))
.then(fs => fs.filter(f => f.endsWith('.js')))
.then(fs => Promise.all(fs.map(extractAddOn)));
function extractAddOn(filename: string): Promise<addon> {
return import(filename).then(contents => ({
name: contents.addonInfo.name,
desc: contents.addonInfo.desc,
run: contents.startAddon
}));
}
Вот и все!
Теперь пара улучшений:
- Соглашение о присвоении имен интерфейсов в PascalCase. Это может включать переименование вашего интерфейса
addon
в AddOn
или Addon
. - Также принято давать имена значений (включая функции) в camelCase. Это может включать переименование вашей функции
loadaddons
в loadAddOns
или loadAddons
. - . Это соглашение (хотя и менее универсальное, чем в предыдущих двух) использовать
T[]
для представления типа "массива значения, каждое из которых имеет тип T
", а не Array<T>
.
Применение этих изменений приводит вас сюда:
import { promisify } from 'util';
export interface AddOn {
name: string;
desc: string;
run: (someParam: any) => void;
}
export function loadAddOns(): Promise<AddOn[]> {
return promisify(readdir)(path.join(__dirname, 'addons'))
.then(fs => fs.filter(f => f.endsWith('.js')))
.then(fs => Promise.all(fs.map(extractAddOn)));
}
function extractAddOn(filename: string): Promise<AddOn> {
return import(filename).then(contents => ({
name: contents.addonInfo.name,
desc: contents.addonInfo.desc,
run: contents.startAddon
}));
}
Надеюсь, что поможет!