打字稿同步功能与导入?

时间:2020-05-01 13:25:57

标签: typescript

// 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()函数未同步,并且在完成将所有插件添加和添加到变量后该函数将不会返回,是的,我可以在.after()中进行返回,但仅当只有一个插件,事实并非如此。等待返回错误,我不知道如何进行;我希望它如何运行:

  • 读取目录
  • 将文件过滤到.js
  • 循环遍历(直到循环完成才返回)
    • 导入
    • 将信息添加到插件var
  • 返回插件变量

注意:打字稿新手

1 个答案:

答案 0 :(得分:0)

从某种意义上说,TypeScript中的异步函数具有“传染性”:一旦在管道中引入了异步部分,就必须几乎总是使整个管道也处于异步状态。这并不全是坏事,因为有一些不错的工具可以使用异步函数。

假设您希望将loadaddons函数修改为异步函数。首先要考虑的是类型签名。现在,它不返回类型为Array<addon>的值,而是返回类型为Promise<Array<addon>>的值。因此,您的新签名类似于:

function loadaddons(): Promise<Array<addon>> {
  // ... to be updated
}

现在,我们需要修改此函数的主体以异步执行。您已经很好地将函数编写为动作的“管道”( read ,然后是 filter ,然后是 act 等),这使得它特别容易转换。考虑一次一次修改管道。

我们首先需要将readdirSync替换为readdirreaddirSync返回Array<string>,而readdir期望函数作为其最终参数;准备好后,它将使用文件名数组调用此函数。我宁愿使用诺言而不是使用回调,幸运的是,将期望回调的函数转换为可返回诺言的函数足够容易。所需要的只是'util'模块中的promisify函数。也就是说,promisify(readdir)(path.join(__dirname, 'addons'))根据需要返回一个承诺。

现在,我们不能仅对filter返回的值直接调用promisify(readdir)方法,因为它是一个promise而不是“常规”值。我们可以使用Promise进行的几乎所有操作都是提供一个函数,以在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,因此我们将提供一个函数,该函数回答假设的假设“假设此承诺将解析为名为contents的JavaScript文件;我将如何从中提取所需的数据? ”。可能看起来像这样:

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接口重命名为AddOnAddon
  2. 这也是一种约定,在camelCase中提供值(包括函数)名称。这将涉及将loadaddons函数重命名为loadAddOnsloadAddons
  3. 使用T[]表示“值数组,每个值的类型为T”而不是{{1 }}。

应用这些更改可将您带到这里:

Array<T>

希望有帮助!