【问题标题】:What is the Rails 6 Way to load a web worker?Rails 6 加载 web worker 的方法是什么?
【发布时间】:2021-08-23 09:25:04
【问题描述】:

我正在使用带有 webpacker 的标准 Rails 6 设置。我正在为 JavaScript 使用 Stimulus,但这并不重要。

对于我的应用程序,我有一个计时器,即使浏览器选项卡未处于活动状态,它也需要继续运行。由于似乎setInterval 可以在选项卡未处于活动状态时停止,因此我潜心编写一个网络工作者。我读过this question,但它的解决方案似乎不适用于现代 Rails。

我希望我可以通过我的 Stimulus 控制器来处理这一切,但我的理解是 web worker 文件需要分开,所以我试图弄清楚如何正确加载它。

我为工人创建了一个文件timer.js。我应该把这个文件放在哪里,我如何引用它的 URL 以便将它作为工作人员启动?

  • 我需要能够在我的一个视图 ERB 文件中呈现其 URL,以便我可以将其 URL 传递给 Stimulus 控制器,然后使用 new Worker(theUrl) 启动工作程序。但是,<%= javascript_path(...) %> 似乎仅适用于资产管道。
  • 我想在我的工作文件中使用import Rails from "@rails/ujs",这样我就可以使用它轻松地发出 AJAX POST 请求,所以我认为工作文件需要以某种方式与 webpack 绑定。

目前我刚刚把它放在public/timer.js,但我在加载时在浏览器控制台中收到错误,所以我认为我做错了:

SyntaxError: import declarations may only appear at top level of a module

在 Rails 6 中加载 Web Worker 的正确方法是什么?

timer.js

这是工作文件的内容,以防万一。 (我知道这很粗糙;这只是一个草稿。)

import Rails from "@rails/ujs";

let timerInterval = null;
let timeRemaining = null;
let postFailures = 0;
let postUrl = null;

function finishActivity() {
  Rails.ajax({
    type: "POST",
    url: postUrl,
    success: () => {
      postFailures = 0;
    },
    error: () => {
      postFailures++;
      if (postFailures < 5) {
        setTimeout(finishActivity, 1000);
      } else {
        alert("Error.");
      }
    },
  });
}

self.addEventListener("message", (event) => {
  if (event.data.timeRemaining) {
    timeRemaining = event.data.timeRemaining;
    if (timerInterval) clearInterval(timerInterval);
    timerInterval = setInterval(() => {
      if (timeRemaining > 0) {
        timeRemaining = timeRemaining - 0.01;
        postMessage({ timeRemaining: timeRemaining });
      } else {
        timerInterval = null;
        clearInterval(timerInterval);
        finishActivity();
      }
    }, 10);
  }
  if (event.data.postUrl) {
    postUrl = event.data.postUrl;
  }
}, false);

【问题讨论】:

  • Webpacker 不会通过 ERB 传递 JS 文件。虽然您可以使用 erb-loader 执行此操作,但重新考虑您的设计并使用附加到元素或 ajax 调用的数据属性将数据从服务器传递到客户端可能是一个更好的主意。在 JS 资产中使用 ERB 通常是一个很好的方法,因为它是在部署时而不是运行时评估的。
  • @max 这是很好的信息。但是,我不需要在这里使用 erb,如果不清楚,抱歉。我实际上是使用数据属性在客户端和服务器之间传递数据(通过刺激)。但我的理解是我的JS需要运行new Worker(urlOfJavaScriptFile)来启动worker,而当文件由webpacker处理时我不知道如何获取该URL。而且我认为我的文件需要由 webpacker 处理,所以我可以打电话给import Rails from "@rails/ujs"

标签: javascript ruby-on-rails web-worker webpacker stimulusjs


【解决方案1】:

我已经有一段时间没有找到解决方案了,但我现在回来分享我的发现。这是我在几个月前从事的宠物项目中采用的解决方案。我不保证这是最好的(甚至是正确的)做事方式,但它对我有用。

我在public/timer_worker.js创建了工作文件:

let timerInterval = null;
let timerTimeout = 10;

function setTimerInterval() {
  if (!timerInterval) {
    timerInterval = setInterval(function() {
      postMessage("timerTick");
    }, timerTimeout);
  }
}

function clearTimerInterval() {
  clearInterval(timerInterval);
  timerInterval = null;
}

onmessage = function(event) {
  if ("run_flag" in event.data) {
    if (event.data["run_flag"]) {
      setTimerInterval();
    } else {
      clearTimerInterval();
    }
  } else if ("set_timeout" in event.data) {
    timerTimeout = event.data["set_timeout"];
  }
}

在我的 webpack 入口点 app/javascript/packs/application.js 中,我在全局 window 对象上初始化了 worker,所以我可以在其他地方引用它:

window.App = {};
App.timerWorker = new Worker("/timer_worker.js");

这是连接工人的一种方法。在此之后,我可以在我的 Stimulus 控制器中使用它。尽管没有 Stimulus 也可以以类似的方式使用 worker。

这是我最终使用的 Stimulus 控制器。当然,其中一些内容是特定于我的用例的。但关键部分是消息处理,它展示了它如何与工作者交互。我没有在这篇文章中以任何方式缩写这个控制器,希望它能更好地描绘功能。

import { Controller } from "stimulus";
import Rails from "@rails/ujs";

export default class extends Controller {
  static targets = [ "timer", "progressBar" ];

  static values = {
    timeRemaining: Number,
    activityDuration: Number,
    postUrl: String,
  }

  connect() {
    App.timerWorker.postMessage({ "run_flag": true });
    this.timerTarget.textContent = Math.ceil(this.timeRemainingValue);
    this.postFailures = 0;

    let controller = this;
    App.timerWorker.onmessage = function() {
      if (controller.timeRemainingValue > 0) {
        controller.timeRemainingValue = controller.timeRemainingValue - 0.01;
      }

      if (controller.timeRemainingValue > 0) {
        controller.timerTarget.textContent = Math.ceil(controller.timeRemainingValue).toString();
        controller.progressBarTarget.style.width = `${(1 - (controller.timeRemainingValue / controller.activityDurationValue)) * 100}%`;
      } else {
        App.timerWorker.postMessage({ "run_flag": false });
        controller.finishActivity();
      }
    }
  }

  finishActivity() {
    let controller = this;
    Rails.ajax({
      type: "POST",
      url: controller.postUrlValue,
      success: () => {
        this.postFailures = 0;
        App.timerWorker.postMessage({ "set_timeout": 10 });
      },
      error: (e, xhr) => {
        this.postFailures++;
        if (this.postFailures < 3) {
          App.timerWorker.postMessage({ "run_flag": true });
          App.timerWorker.postMessage({ "set_timeout": 1000 });
        } else if (this.postFailures < 5 || xhr === "") {
          App.timerWorker.postMessage({ "run_flag": true });
          App.timerWorker.postMessage({ "set_timeout": 60000 });
        }
      },
    });
  }
}

【讨论】:

    猜你喜欢
    • 2021-07-24
    • 2013-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-27
    • 2019-05-26
    • 2012-06-27
    • 2023-03-10
    相关资源
    最近更新 更多