Rails 6 加载 web worker 的方法是什么?
What is the Rails 6 Way to load a web worker?
我正在使用带有 webpacker 的标准 Rails 6 设置。我正在为 JavaScript 使用 Stimulus,但这并不是太重要。
对于我的应用程序,我有一个计时器需要保持 运行 即使浏览器选项卡未处于活动状态。因为似乎 setInterval
可以在选项卡不活动时停止,所以我开始编写一个网络工作者。我读过 this question,但它的解决方案似乎不适合现代 Rails。
我希望我可以通过我的 Stimulus 控制器来处理这一切,但我的理解是网络工作者文件需要分开,所以我想弄清楚如何正确加载它。
我已经为工作人员创建了一个文件,timer.js
。我应该把这个文件放在哪里,我怎样才能引用它 URL 以便作为一个工作人员启动它?
- 我需要能够在我的一个视图 ERB 文件中呈现它的 URL,这样我就可以将它的 URL 传递给 Stimulus 控制器,然后用 [=15= 启动工作器].但是,
<%= 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);
我已经有一段时间没有找到解决这个问题的方法了,但我现在回来分享我的发现。这是我在几个月前从事的宠物项目中使用的解决方案。我不保证这是最好的(甚至是正确的)做事方式,但它对我有用。
我在 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"];
}
}
在我位于 app/javascript/packs/application.js
的 webpack 入口点中,我在全局 window
对象上初始化了 worker,因此我可以在其他地方引用它:
window.App = {};
App.timerWorker = new Worker("/timer_worker.js");
这是联系工作人员的一种方式。在此之后,我可以在我的 Stimulus 控制器中使用它。尽管可以在没有 Stimulus 的情况下以类似的方式使用 worker。
这是我最终使用的 Stimulus 控制器。当然,其中一些内容是特定于我的用例的。但关键部分是消息处理,它演示了它如何与 worker 交互。我没有为此 post 以任何方式缩写此控制器,希望它能更好地描绘功能。
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 });
}
},
});
}
}
我正在使用带有 webpacker 的标准 Rails 6 设置。我正在为 JavaScript 使用 Stimulus,但这并不是太重要。
对于我的应用程序,我有一个计时器需要保持 运行 即使浏览器选项卡未处于活动状态。因为似乎 setInterval
可以在选项卡不活动时停止,所以我开始编写一个网络工作者。我读过 this question,但它的解决方案似乎不适合现代 Rails。
我希望我可以通过我的 Stimulus 控制器来处理这一切,但我的理解是网络工作者文件需要分开,所以我想弄清楚如何正确加载它。
我已经为工作人员创建了一个文件,timer.js
。我应该把这个文件放在哪里,我怎样才能引用它 URL 以便作为一个工作人员启动它?
- 我需要能够在我的一个视图 ERB 文件中呈现它的 URL,这样我就可以将它的 URL 传递给 Stimulus 控制器,然后用 [=15= 启动工作器].但是,
<%= 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);
我已经有一段时间没有找到解决这个问题的方法了,但我现在回来分享我的发现。这是我在几个月前从事的宠物项目中使用的解决方案。我不保证这是最好的(甚至是正确的)做事方式,但它对我有用。
我在 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"];
}
}
在我位于 app/javascript/packs/application.js
的 webpack 入口点中,我在全局 window
对象上初始化了 worker,因此我可以在其他地方引用它:
window.App = {};
App.timerWorker = new Worker("/timer_worker.js");
这是联系工作人员的一种方式。在此之后,我可以在我的 Stimulus 控制器中使用它。尽管可以在没有 Stimulus 的情况下以类似的方式使用 worker。
这是我最终使用的 Stimulus 控制器。当然,其中一些内容是特定于我的用例的。但关键部分是消息处理,它演示了它如何与 worker 交互。我没有为此 post 以任何方式缩写此控制器,希望它能更好地描绘功能。
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 });
}
},
});
}
}