машинопись синхронная функция с импортом? - PullRequest
0 голосов
/ 01 мая 2020
// addons.ts

export interface addon {
  name: string;
  desc: string;
  run: (someparam: any) => void;
}

export function loadaddons(): Array<addon> {
  let addons: Array<addon> = [];
  fs.readdirSync(path.join(__dirname, "addons"))
    .filter((file) => file.endsWith(".js"))
    .forEach((file) => {
      import(path.join(__dirname, "addons", file)).then((imported) => {
        addons.push({
          name: imported.addonInfo.name,
          desc: imported.addonInfo.desc,
          run: imported.startAddon,
        });
      });
    });
  return addons;
}


// index.ts

import { loadaddons } from "./addons";
let addons = loadaddons();
addons.forEach((addon) => {
  addon.run("someparam");
});


// example addon

export const addonInfo = {
  name: "exampleaddon",
  desc: "an example addon",
};

export function startAddon() {}


// output

[]


// wanted output

[
  {
    name: 'exampleaddon',
    desc: 'an example addon',
    run: [Function: startAddon]
  }
]

Проблема в том, что функция import () не синхронизирована c, и функция не вернется после завершения импорта и добавления всех дополнений в переменную, да, я могу сделать возврат в .after () но это работало бы, только если бы был один аддон, ну, это не так. await возвращает ошибку, и я не знаю, как продолжить; как я хочу, чтобы он выполнялся:

  • чтение каталога
  • фильтрация файлов в. js
  • цикл через (не возвращаться, пока цикл не завершится)
    • import
    • добавить информацию в аддоны var
  • вернуть переменную addons

примечание: новинка для машинописи

1 Ответ

0 голосов
/ 01 мая 2020

Асинхронные функции в 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
  }));
}

Вот и все!

Теперь пара улучшений:

  1. Соглашение о присвоении имен интерфейсов в PascalCase. Это может включать переименование вашего интерфейса addon в AddOn или Addon.
  2. Также принято давать имена значений (включая функции) в camelCase. Это может включать переименование вашей функции loadaddons в loadAddOns или loadAddons.
  3. . Это соглашение (хотя и менее универсальное, чем в предыдущих двух) использовать 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
  }));
}

Надеюсь, что поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...