使用来自两个 fetch/then 链的元素构建事件处理程序

Build an event handler using elements from two fetch/then chains

背景和目标

我有两个 fetch/then 链,它们构建事件处理程序所需的元素。

第一个链加载数据以构建 <select> 元素。默认情况下选择某些选项。 then 中的函数没有 return 任何东西。它只在 DOM.

中创建 <select> 元素
fetch("data/select.json")
  .then(response => response.json())
  .then(data => {
    // populate select options
  });

第二个链加载数据以使用Chartist绘制多个图表。

fetch("data/chartist.json")
  .then(response => response.json())
  .then(data => Object.keys(data).forEach(x =>
    // draw charts using Chartist
    new Chartist.Line(`#${x}`, { "series": data[x] });
  ));

最后,我需要为每个图表对象添加一个事件处理程序(在使用 Chartist's on method"created" 事件上)。处理函数需要获取在第一个 fetch/then 链中构建的 <select> 元素中选择的值。由于每次(重新)绘制图表(例如图表创建、window 调整大小等)时都会发出 "created",因此用户可能同时选择了与默认值不同的值。因此,我不能只使用包含默认值的静态数组。相反,我从 DOM(即 selectElement.selectedOptions)中的 <select> 元素的属性中获取所选值,因此我需要等待 <select> 元素准备就绪.

我试过的

目前,我将事件处理程序添加到第二个 fetch/then 链中的每个图表对象。但是,当在 <select> 元素准备好之前执行获取所选选项的代码时,这会失败。

fetch("data/chartist.json")
  .then(response => response.json())
  .then(data => Object.keys(data).forEach(x => {
    // draw charts using Chartist
    const chart = new Chartist.Line(`#${x}`, { "series": data[x] });
    // add an event handler to the charts
    chart.on("created", () => {
     // get the values selected in the <select> and use it
    });
  }));

我还尝试将第二个 fetch/then 链嵌套在第一个链中或添加超时。这不是最佳选择,因为它不会通过异步获取数据来浪费时间。

问题

我想最干净的解决方案是从第二个 fetch/then 链中提取事件处理程序,让两个 fetch/then 链 运行 异步,并且只在添加事件处理程序之前等待。这是一个好方法吗?

如果是,我认为我需要做出 then 的 return 承诺并等待(例如使用 Promise.all)。一个承诺应该 return 图表对象不会丢失任何事件(特别是图表创建时发出的第一个 "created" 事件),所以我可以在第二个 fetch/[= 之外提取事件处理程序15=]链.

我阅读了很多关于 SO 的相关问题(例如关于 asynchronous programming and promises), as well as the meta thread 关于关闭此类问题。我知道这个理论已经被彻底解释了好几次,但是我没有设法对我的问题实施一个干净的解决方案尽管仔细阅读了问题(这里是初学者)。任何提示将不胜感激。

您正在 运行进入所谓的 race condition。承诺的解决顺序是不确定的。

可以使用Promise.all()等待两个获取请求解决:

Promise.all([
  fetch('data/select.json').then(r => r.json()),
  fetch('data/chartist.json').then(r => r.json())
]).then(results => {
  // populate select options from results[0]
  Object.keys(results[1]).forEach(x => {
    let chart = new Chartist.Line(`#${x}`, { "series": results[1][x] });
    chart.on('created', event => {
      // get the values selected in the <select> and use it
    });
  });
});

但与嵌套提取一样,此方法会在您的应用程序中增加不必要的延迟。您的 select 元素数据可以在图表数据之前收到,但您必须等待第二个承诺解决,而不是提前更新 UI。此外,这两个请求都必须成功,否则您的代码将不会 运行。您可以使用 Promise.allSettled() 并手动处理任何错误情况 但是 您仍然需要等待两个请求完成才能执行代码。

处理此问题的更好方法是在填充 select 元素时通过扩展 EventTarget and dispatching a CustomEvent 来利用网络的 事件驱动 特性。

因为我们知道我们的自定义 populated 事件有可能在 Chartist 获取解决之前触发,所以我们还需要使用几个布尔值来跟踪状态。如果我们错过了事件,只需配置图表,否则附加一个 EventListener 并等待,但前提是还没有侦听器排队。

在新文件中:

export class YourController extends EventTarget {
  constructor() {
    super();
  }

  populated = false;

  async getData(path) {
    return fetch(path).then(res => res.json());
  }

  async getSelectOptions() {
    let data = await this.getData('data/select.json');
    // populate select options here
    this.populated = true;
    this.dispatchEvent(new CustomEvent('populated'));
  }

  async getChartData() {
    let data = await this.getData('data/chartist.json');
    Object.keys(data).forEach(x => this.createChart(x, data[x]));
  }

  createChart(id, data) {
    let chart = new Chartist.line(`#${id}`, { series:data });
    chart.on('created', event => this.onCreated(chart));
  }
  
  onCreated(chart) {
    if (this.populated) {  this.configure(chart); }
    else if (chart.waiting) { return; } chart.waiting = true;
    this.addEventListener('populated', event => this.configure(chart), { once:true });
  }

  configure(chart) {
    // get and use selected options here
    chart.waiting = false;
    this.dispatchEvent(new CustomEvent('configured', { detail:chart }));
  }
}

在您的应用中:

import { YourController } from './your-controller.js'

const c = new YourController();

c.addEventListener('configured', event => {
  console.log('configured:', event.detail)
});

c.getSelectOptions();
c.getChartData();

addEventListener() 中设置 { once:true } 选项将在 populated 事件第一次触发后自动删除侦听器。由于 created 事件可以触发多次,这将防止无休止地调用 .configure(chart) 堆积起来。我还更新了脚本,以防止在 createdpopulated.

之前多次触发时添加额外的侦听器