質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.37%

  • Node.js

    1219questions

    Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

sleepを条件付きで解除したい

解決済

回答 2

投稿 2017/11/21 18:44

  • 評価
  • クリップ 0
  • VIEW 129

ponzu1990

score 10

ぐぐっても見かけなかっので質問させてください。

ある一定の条件を満たすとsleepから抜けるものを考えているのですが、
本当にこれでいいのか疑問を持っています。

flag = 0; //global - 他の部分で状態が変化する
s = 30;
var e = new Date().getTime() + (s * 1000);
while(new Date().getTime() <= e) {
  if(flag !=0) break;
}


このコードは30秒間sleep(CPU燃焼?)し、
sleep中であってもflagの値が変化したらsleepをやめるものです。
setTimeout()だと状態変化に対応できないので…。

使用するnode.jsのversion、及びnpmパッケージは問いません。

もし良い方法がありましたら、教えていただけますと大変ありがたいです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

この質問文は「スリープを実装したいから」頑張って作ったのでしょうか?
それともビジーループを実装したいから実験したいのでしょうか?
後者が目的ならアドバイスは役に立たないので読み飛ばしてください。

もし前者なら、下記のアドバイスが役に立つかもしれません。


JavaScriptやNode.jsにはsleepというコマンドはありません。
何故ならシングルスレッドだから、sleepしている間全ての作業がストップしてしまうからです。

スタートがブラウザなので、ブラウザから要素をクリックしたぞとか、要素の上をマウスカーソルが通過したぞみたいなイベントの山をバンバン処理しなきゃいけないですからね。
その代わりにイベントが来るまで怠惰に休むという仕組みを持っています。

JavaScriptの世界では、
Sleepの代わりにやりたいことを予め関数として用意しておいて、
setTimeoutという関数と同時に利用します。

flag = 0; // global - 他の部分で状態が変化する
var s = 30;
var fn = function() {
  if (flag != 0) return;
  // dosomething  
}
setTimeout(fn, s * 1000);

30秒後に起動した際、「ありゃ、フラグが0から変更されてるじゃん…起動やめよっと」と
判断して内部の処理を取りやめるフローに変更しています。


【ここから追記】

sleep中であってもflagの値が変化したらsleepをやめるものです。

つまり、スリープをやめて今すぐ発火して欲しいのですね。
私の上の回答文ではやりたい事は満たせないですね……
そのように作り変えましょう!

setTimeoutの戻り値のIDを利用して、下記の様に登録したsetTimeoutを取り消す事も可能です。
つまり、登録はしておいたけどやっぱりキャンセルは可能なんですね。
後で使うので覚えておいてください。

var fnId = setTimeout(fn, s * 1000);
clearTimeout(fnId);

そしてMobXというパッケージを導入しましょう。
参考サイト: これからMobXをはじめる人へ

MobXのパッケージはReactという動的なページを表示するフレームワークのおまけみたいな扱いをされますが、本質は変数のオブザーバにあります。
オブザーバというのは変数を常に監視して、値が変わったら即座に関数を実行するデザインパターンを指します。

30秒後に登録しておいたsetTimeoutの関数を取り下げつつ、今すぐ実行します。
因みにMobXに限らず自分で作ったイベントエミッター系は同期実行なのでsetTimeoutに割り込まれる事はなく、
30秒と同時にフラグが経って2回関数が叩かれるという残念な事は起こりません。

以前私がイベントループを上手く回す為にベンチマークと称して使ってたコードがありますので、
流用してサクッと実装してみましょう。

const {observable, autorun, reaction} = require('mobx');
global.state = {
  id: null
};
global.store = observable({
  flag: 0 // globalのflag変数はこっちに引っ越しました。少し使い勝手が変わるので注意
});

const fn = function() {
  if (state.id == null) return;
  state.id = null;
  // do something
}
state.id = setTimeout(fn, 31000);
reaction(
  () => store.flag,
  () => {
    if (store.flag === 0) return;
    if (state.id) clearTimeout(state.id); // 先程実行したものを取り下げて
    fn(); // 手動実行!
  }
)

ざっとこんな感じですかね。

投稿 2017/11/21 20:11

編集 2017/11/21 21:00

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/21 20:20

    実は私の解答はKSwordOfHasteさんとつながっています。

    予め関数を作って置かなければならないので、
    JSやNode.jsは非同期処理が増えると所謂コールバック地獄というネストだらけになってしまいます。

    そこでPromiseやAsync/Awaitという仕組みが用意されており、
    簡潔に非同期処理を順に実行していく流れをスマートに書けるようになっています。
    やりたい事が高度になってソースコードがぐちゃぐちゃになったら検討してみてください。

    キャンセル

  • 2017/11/21 21:01 編集

    あ、そうか…きっとこれはフラグがONになったら発火してほしいんですね。
    回答文を見直して修正かけておきます。 -> 修正しました。これで実現出来るはずです!

    キャンセル

  • 2017/11/21 21:05

    Reactの何たるかを分かっていないのですがobserverという単語から「ポーリングでなくイベントで」というところをmiyabi-sunさんは示唆しておられるのだろうかと想像しました。
    質問者さんがどのような状況なのか興味がわきます・・・

    キャンセル

  • 2017/11/21 21:19 編集

    そうですねえ、そもそも目的が何個かの処理を並列で走らせて、
    全部終わったら完了処理を走らせたいだけかもしれないですからね。
    その場合ふたりともはずれで、Promiseで書けばいいじゃんって話ですよね。

    キャンセル

  • 2017/11/21 21:48

    先日Promise.raceって何に使うんだろうと不思議に感じたのですが、ひょっとして「30秒timeoutか、目的タスクが終わるか」どちらか一方を待つようなときにraceの出番なのかと今気づきました。質問者さんの状況がもしそうならポーリングでなくてPromiseでも書けそうな気がします。

    キャンセル

  • 2017/11/22 17:52 編集

    300秒の間にユーザーに[yes]/[no]の選択をしてもらい、タイムアウトまたは[no]だったら後続処理に行く…ということがしたかったのです。初めてPromise.raceを今はじめて知って見てみましてやりたいことがかなりできそうなのですが、ユーザーの応答をsocket.onで受け取る仕組みのため、raceで実装するのは難しいと思いました…。あとmiyabi-sunさんがご教示してくださったMobXが今回の件でもかなり使えるので活用していきたいと思います。みなさま本当に有難うございます。

    キャンセル

  • 2017/11/22 16:12 編集

    後から発生する割り込み的な機能の実装なのですね!
    こうしたもの(単純な非同期の連鎖でないもの)はPromiseを使う制御よりかえってmobxのようなobservableによる実装の方が直感的で分かり易い気がしました。
    勉強になりました。>miyabi-sunさん
    最初質問から受けた印象からは想像できなかった点で勉強させていただき感謝しています。質問に「後から発生する事象により非同期処理の制御を変更」みたいな表現があると察しの悪い自分でもポイントがつかみやすかったとも感じましたw;>質問者さん。

    キャンセル

  • 2017/11/22 16:14

    はずかしながら、自分にとってmiyabi-sunさんの回答の方が有意義な印象ですw;

    キャンセル

  • 2017/11/22 16:28

    いえいえ、Promise.raceという新しい発見もありましたし、
    殆どのエンジニアはKSwordOfHasteさんのようにPromise主体で考えるでしょう。
    なので、もし後続の実装で詰まった時は解答が得られやすいんですよね。

    既存の質問文のwhileを残した辺りも質問者寄りの印象としては良かったと思います。
    安心感が違いますね。
    私のはイベントループ使えという方向に持っていく為にポーリング全否定してますから、フォローするために長文になってしまいました。

    私が蛇の道は蛇といった感じで、
    たまたま使ってたMobXライブラリが今回刺さっただけという印象ありますね。
    今後の事を考えたらどうだか分からんってのが感想です(/ω\)

    キャンセル

  • 2017/11/24 10:05

    おふた方の情報が有益すぎて、どちらにもベストアンサーをつけたかったのですが…知りたいこと以上のことを知ることができたmiyabi-sunさんの回答につけさせていただきました。KSwordOfHasteさん、その名の通り素早く的確なレスありがとうございました。

    キャンセル

  • 2017/11/24 10:09

    BAはしかるべき回答につけていただくのが一番だと思います!
    自分もとても勉強になり感謝しているのです。

    キャンセル

+2

これはビジーループと呼ばれるものですね。ぐぐっても見つからないのは「ビジーループは特別な理由がない限り普通はしないもの」だからだと思います。シングルスレッドのECMAScriptではなおのことではないでしょうか?

どのような状況かよくわかりませんが、例えばsetTimerにより短い時間間隔ごとに状態変化をチェックするようにしてはいかがでしょうか?

また、もし非同期処理(setTimerなど)を上から下へ同期的に実行されるかのように書きたいならPromise/async/awaitを使うのも一つの方法と思います。

function promissSleep(t) {
  return new Promise(resolve => {
    setTimeout(resolve, t);
  });
}

var flag = 0;

// async関数の中では非同期処理を同期的に書ける
async function waitForFlag(s) {
  var e = new Date().getTime() + (s * 1000);
  while (new Date().getTime() <= e) {
    await promissSleep(100); // 10msずつsleep=>あまりに頻繁すぎるので100msに...
    if (flag !== 0) break;
  }
}

// 呼び出し元もasync関数にする。
// この関数の呼び元もasync...
async function foo() {
  await waitForFlag(30);
  ...
}

ご参考:
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function

投稿 2017/11/21 19:49

編集 2017/11/21 20:45

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/21 20:05

    promissSleepで無条件に1秒まつようになってたのでコードを訂正させていただきました。失礼しました。

    キャンセル

  • 2017/11/21 20:19

    早くて的確な回答をありがとうございます。これなら無駄な負荷をかけずにやりたいことができそうなので使用させていただきます。ありがとうございます<(╹ヮ╹)>

    キャンセル

  • 2017/11/21 20:33

    ん〜…10msは明らかに連打しすぎで、
    whileのループより多少マシ程度でイベントループも汚れますし負荷もあまり減ってないと思われます。

    キャンセル

  • 2017/11/21 20:43

    たしかに・・・普通のWindowsとかですとほぼ最小のインターバルということになりそうです。せめて100msぐらいにしておけば多少行儀の悪さは軽減されるでしょうか・・・

    キャンセル

  • 2017/11/21 20:47

    そうですね、対応ありがとうございます!

    キャンセル

  • 2017/11/21 20:49

    ご指摘ありがとうございました。

    キャンセル

  • 2017/11/22 09:46

    ありがとうございます、私も100msで運用したいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.37%

関連した質問

同じタグがついた質問を見る

  • Node.js

    1219questions

    Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。