【问题标题】:Can I wait for a DOM event to resolve?我可以等待DOM事件化解?
【发布时间】:2020-03-12 19:28:36
【问题描述】:

我犯了一个错误。我将我的功能与.on('click', ...) 事件配对。我的系统安装了某些项目,每个项目都进行了分类。目前,我的类别是[post, image, widgets],每个类别都有自己的进程,它们在前端以列表的形式表示。下面是它的外观:

正如我所说,每一个都与点击事件配对。当用户点击Install时会出现一个漂亮的加载器,<li>本身有时尚的变化等等。

我也碰巧有一个按钮,应该允许用户安装所有项目:

这很好。除了......如果不模拟用户点击,绝对没有办法做到这一点。这很好,但是,我如何才能等待每个项目完成(或不完成),然后再继续下一个?

如何向外界发出安装过程已完成的信号?

感觉如果我使用new CustomEvent,这将开始变得难以理解。

这是我想要实现的一些代码:

const installComponent = () => {

  return new Promise((resolve, reject) => {
    setTimeout(() => {
      return resolve();
    }, 1500);
  });
};

$('.item').on('click', (event) => {
  installComponent().then(() => {
    console.log('Done with item!');
  });
});

$('#install-all').on('click', (event) => {
  const items = $('.item');
  items.each((index, element) => {
    element.click();
  });
});
ul,
ol {
  list-style: none;
  padding: 0;
  margin: 0;
}

.items {
  display: flex;
  flex-direction: column;
  width: 360px;
}

.item {
  display: flex;
  justify-content: space-between;
  width: 100%;
  padding: 12px 0;
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
  margin: 0;
}

.item h3 {
  width: 80%;
}

.install-component {
  width: 20%;
}

#install-all {
  width: 360px;
  height: 48px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="items">
  <li class="item" data-component-name="widgets">
    <h3>Widgets</h3>
    <button class="install-component">Install </button>
  </li>
  <li class="item" data-component-name="post">
    <h3>Posts</h3>
    <button class="install-component">Install </button>
  </li>
  <li class="item" data-component-name="images">
    <h3>Images</h3>
    <button class="install-component">Install </button>
  </li>
</ul>
<button id="install-all">Install All</button>

如您所见,所有点击都是同时启动的。没有办法等待触发的任何点击完成。

【问题讨论】:

  • 不看代码很难知道,但你可以看看回调函数。
  • @Edit 我可以发布一些代码,但坦率地说,这是一个非常普遍的问题,我认为添加代码不会起到任何作用。我有一个li,每个li 我都有一个button,我点击它。我的目标是通过代码单击这些按钮中的每一个,但不要在完成之前单击。
  • @Edit 添加的代码。
  • "除了......如果不模拟用户点击,绝对没有办法做到这一点。" - 停在那里重新考虑。在您展示的示例代码中,您可以像从 install-one 事件处理程序中调用它一样轻松地从 install-all 事件处理程序调用 installComponent()
  • @DanielSmith 我不认为事件处理程序可以return 任何东西。一个技巧是让他们将结果存储在您之后访问的变量中。但实际上,正确的方法是从多个事件处理程序中调用相同的底层控制器函数,而不是触发点击事件。

标签: javascript


【解决方案1】:

这是您的应用程序的简单架构问题,可以通过研究属于 MVC、Flux 等的模式来解决。

我非常推荐 Flux,因为它易于理解,而且您可以通过商店和操作将事件和 UI 分开来解决您的问题。

在这种情况下,您将在单击任何这些按钮时触发一个操作。该操作可以立即更新您的商店以将 UI 设置为加载状态,该状态禁止单击其他任何内容并显示加载器。然后,该操作将处理可以通过 Promise 监视的加载程序,完成后,该操作将通过将商店中的加载状态设置为 false 来完成,并且 UI 可以再次恢复正常。正确分离的最酷之处在于,操作将是简单的 JS 方法,如果您愿意,您可以调用这些方法来安装所有元素。从本质上讲,现在将事物解耦将使您的生活更轻松。

对于像点击加载等待完成这样简单的事情来说,这听起来非常复杂和冗长,但这就是 react、angular、flux、redux、mobx 等都试图为你解决的问题。

在这种情况下,我强烈建议使用现代 ECMaScript async/await 来检查 React 和 Mobx,以快速使这个问题和未来的设计决策变得更加容易。

【讨论】:

  • 相信我,我知道而且我每天都讨厌我的存在,但我不能。与此同时,我能做些什么来修补这个问题?我已经分离了我的端点功能,这个......我做得很好。我的 UI 没有连接到我的后端进程。我有大约 3500 LoC 的 UI 代码,我可以随时替换,暂时我被困在这里。
  • Bergi 有最好的补丁解决方案。事件的方法本质上是可以在 JS 中调用的方法,无需模拟点击事件。如果您必须模拟点击事件,只需使用 jquery 来查询和 el.click() 来模拟每个项目的点击。
  • 是的,但是,正如我在示例中所展示的那样,问题是我不能让每一个都完成,然后再触发下一个。
  • 只需在方法范围之外创建一个变量并将 installCompknent 承诺分配给该变量。然后这两种方法都可以等待该承诺解决,然后再进行下一个。只要知道它可能会产生一些你必须小心的陷阱。
  • 另一种选择是创建一个处理队列,该方法将命令添加到队列而不是立即处理。然后,处理队列可以按照注入的顺序一次一个地要求每个命令。如果被调用的方法要求用户操作上下文有效,这可能会失败。
【解决方案2】:

您应该做的是声明一个变量,该变量将存储正在进行的安装。并且在您尝试安装时会在一次安装完成之前对其进行检查。

var inProgress = false;
    const installComponent = () => {
      inProgress = true;
      return new Promise((resolve, reject) => {
        if(inProgress) return;
        else{
          setTimeout(() => {
            inProgress = false;
            return resolve();
          }, 1500);
        }
      });
    };

【讨论】:

  • return resolve() 中的return 没用。您不能 return 异步回调函数中的任何内容。
【解决方案3】:

我希望实现这样的功能:

let $items = $('.items .item');
let promises = new Array($items.length);

// trigger installation of the i'th component, remembering the state of that
function startInstallOnce(i) {
    if (!promises[i]) {
        let component = $items.get(i).data('component-name');
        promises[i] = installComponent(component);
    }
    return promises[i];
}

// used when a single item is clicked
$items.on('click', function(ev) {
    let i = $(this).index();
    startInstallOnce(i);
});

// install all (remaining) components in turn
$('#install-all').on('click', function(ev) {
    (function loop(i) {                      // async pseudo-recursive loop
       if (i === components.length) return;  // all done
       startInstallOnce(i).then(() => loop(i + 1));
    })(0);
});

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-19
    • 2016-05-09
    • 2017-09-30
    • 1970-01-01
    • 2016-02-23
    • 1970-01-01
    相关资源
    最近更新 更多