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

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

新規登録して質問してみよう
ただいま回答率
85.50%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

1594閲覧

複数の関数を時間差で実行してさらに繰り返す方法

umauman

総合スコア57

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

3クリップ

投稿2018/05/16 20:05

前提・実現したいこと

複数の関数を時間差で実行してそれを繰り返す方法を知りたく思っています。

#####実行内容
console.logで下記を表示

(1秒後)コーヒーを注文する→(2秒後)支払いをする→(5秒後)飲む→(1秒後)コーヒーを注文する
→(2秒後)支払いをする→(5秒後)飲む・・・エンドレスにこの処理

※本当はもっと色々な処理をさせたいのですが質問しやすいようにシンプルにしました。

検索しているとこのような処理に「Promise」を使用することをすすめている記事が多く、ひとまず自分なりに記述をしてみました。
ただ、これを繰り返し実行する方法がわからずにいます。

###知りたいこと

  • 表題の「時間差で実行、さらに繰り返し」処理を行うにはどのような記述を行うのが良いのか
  • Promiseで処理をする選択は良いのか?その場合繰り返しをさせるにはどのように記述するべきか
  • そもそも私のPromiseの記述に誤りが無いか、もしくはもっと良い記述があればご指摘いただけると助かります

JavaScript

1function orderCoffee() { 2 return new Promise((resolve, reject) => { 3 setTimeout(() => { 4 console.log('コーヒーを注文する'); 5 resolve(); 6 }, 1000); 7 }); 8} 9function payBill() { 10 return new Promise((resolve, reject) => { 11 setTimeout(() => { 12 console.log('支払いをする'); 13 resolve(); 14 }, 2000); 15 }); 16} 17function drinkCoffee() { 18 return new Promise((resolve, reject) => { 19 setTimeout(() => { 20 console.log('飲む'); 21 resolve(); 22 }, 5000); 23 }); 24} 25 26//下記を繰り返し実行させたい 27orderCoffee() 28 .then(payBill) 29 .then(drinkCoffee)

どうぞ宜しくお願いします

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

Q1. 「時間差で実行、さらに繰り返し」処理を行うには?

とりあえず、Promiseを使う前提で書いて見たコードが以下となります。

Pattern A

イメージ説明
animationGifで再生

const asyncOrderDrink = () => { return new Promise(resolve => { setTimeout(() => { console.log('コーヒーを注文する') resolve(); }, 1000) }); } const asyncPayBill = () => { return new Promise(resolve => { setTimeout(() => { console.log('料金を支払う') resolve(); }, 2000) }); } const asyncDrinkCoffee = () => { return new Promise(resolve => { setTimeout(() => { console.log('コーヒーを飲む') resolve(); }, 3000) }); } function* makeLoopGenerator (fns) { while(true) { for (var fn of fns) { yield fn(); } } } function runLoop(generator) { function loop() { generator.next().value.then(loop); } loop(); } runLoop( makeLoopGenerator([asyncOrderDrink, asyncPayBill, asyncDrinkCoffee]) );

Pattern B(追記)

質問者さんの前回の質問を見る限りでは、単に時間差で複数の同期処理を実行させたいように見受けられるので、maisumakunさんの回答にあるようにsleep関数のようなものを作って、各同期処理の前で実行してあげるのが良さそうです。

ということで、maisumakunさんの回答を参考に以下、generator版のコードを追加。

const sleep = (delay) => { return new Promise((resolve) => { setTimeout(resolve, delay) }); } const orderCoffee = (v) => console.log(v); const payBill = (v) => console.log(v); const drinkCoffee = (v) => console.log(v); function runLoop(gen) { function loop() { gen.next().value.then(loop); } loop(); } function* makeLoopGenerator() { while(true) { yield sleep(1000); orderCoffee('コーヒーを注文する'); yield sleep(2000); payBill('料金を支払う'); yield sleep(3000); drinkCoffee('コーヒーを飲む'); } } runLoop(makeLoopGenerator())

※ 追記のコードも最初のコードのように、ループ内で実行させたい関数が可変の場合でも対応できるようにしたかったですが、時間切れの為、非対応としました。

Q2. Promiseで処理をする選択は良いのか?

イメージ説明
-> animationGifで再生

質問者さんの前回の質問の場合は、「一定間隔」で複数の(異なる)処理を実行させたいというものだったので、Promiseを使わずに単に、setTimeout()関数で定期実行させてあげるだけで良いかと思います。

以下、前回の質問の「異なる複数の処理」に対応したコードです。

以下を1000秒毎に実行

  • 背景色を変える関数を実行
  • 背景色を変える関数を実行
  • テキストを変える関数を実行
  • 背景色を変える関数を実行
  • 背景色を変える関数を実行
  • テキストを変える関数を実行
  • 上記の繰り返し

html

<div id="containerA" class="spring"></div> <div id="containerB">-----</div>

CSS

#containerA { width: 100px; height: 100px; } #containerB { width: 100px; background: gray; font-size: 20px; text-align: center; } .spring { background-color: pink; } .summer { background-color: red; } .autumn { background-color: brown; } .winter { background-color: blue; }

JavaScript

(function(global, document) { function makeThunk(func, ...rest) { return function() { return func(...rest); } } const app = (function() { function changeClassName(className, element) { element.className = className; } function changeText(value, element) { element.textContent = value; } function* makeLoopGenerator(...funcs) { while(true) { for (let func of funcs) { yield func(); } } } function runLoop(generator) { generator.next(); setTimeout(runLoop, 1000, generator); } function init() { const containerA = document.getElementById('containerA'); const containerB = document.getElementById('containerB'); const funcs = [ makeThunk(changeClassName, 'spring', containerA), makeThunk(changeClassName, 'summer', containerA), makeThunk(changeText, 'HELLO', containerB), makeThunk(changeClassName, 'autumn', containerA), makeThunk(changeClassName, 'winter', containerA), makeThunk(changeText, 'WORLD', containerB) ]; runLoop(makeLoopGenerator(...funcs)); } return { init: init } })(); document.addEventListener('DOMContentLoaded', app.init); })(window, document);

Q3. そもそも私のPromiseの記述に誤りが無いか、もしくはもっと良い記述があればご指摘いただけると助かります

回答に載せたコードでもエラー対応をするためのコードは省略していますが、エラーが発生する可能性があるのであれば、new Promise()に渡すコールバック関数内で、非同期処理が失敗した時にreject()が実行されるようにコードを書いてあげる必要があります。

参考

掲載コードの中でgeneratorspread syntaxrest parametersを使っていますが、詳細やブラウザ対応状況は以下でご確認ください。(英語記事へのリンクになっていますが、リンク先ページで言語切り替えが可能です。)

投稿2018/05/16 23:57

編集2018/05/17 10:04
HayatoKamono

総合スコア2415

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

umauman

2018/05/17 05:58

とても丁寧なご回答感謝いたします。 ジェネレーターのことは初めて知りました。(function*)←一瞬間違いかと思ってしまいました。 調べてみましたが定義をしてループに渡す流れ、そしてyieldは一旦処理を止めるものなのですね。 sleep関数も勉強になりました。他の言語だとずばりこのような関数があるのですね。 前回の質問への回答もありがとうございます。(ちょっと違う処理を間にはさんでくださっていてありがとうございます) rejectされたときの場合の処理も記述した方がいいことも理解しました。 全体的に恐らく運用管理面についても考慮されているソースをのだろうなという印象を受けました。 とても参考になりそうです。本当にありがとうございました!
HayatoKamono

2018/05/17 06:23

昨今のJavaScriptを使った開発ではまだブラウザーに実装されていないJavaScriptの新機能を使えるようにbabelといったコンパイラーやwebpackなどのバンドラーを通して、ブラウザで実行可能なコードに変換をすることが多いです。それらを使えばJSの新機能を積極的に使うことが出来るので、興味があれば調べてみると良いかもしれません。
umauman

2018/05/17 13:59

実務の場ではそのような感じでES6を取り入れていっているのですね。 ご回答いただいた内容が色々と調べるのに良いとっかかりになりそうです。 再度、本当にありがとうございました!
guest

0

async-awaitを使えば、「非同期な無限ループ」も、ものすごくすっきり書けます。

javascript

1function sleep(time){ 2 return new Promise(resolve => { 3 setTimeout(resolve, time); 4 }); 5} 6 7async function main(){ 8 for(;;) { 9 await sleep(1000); 10 console.log('コーヒーを注文する'); 11 await sleep(2000); 12 console.log('支払いをする'); 13 await sleep(5000); 14 console.log('飲む'); 15 } 16} 17 18main();

なお、IE(11でも非対応)やiOS 10.3未満では非対応なので(Can I use)、そういう環境ではBabelなどで変換することが必要です。


Promiseで処理をする選択は良いのか?その場合繰り返しをさせるにはどのように記述するべきか

Promiseは単なるライブラリの域を超えて、ジェネレーターやasync-awaitといった文法構造とも結びついている(上のコードでもnew Promiseの結果をawaitで「待たせて」います)、fetchPromiseで結果を返すなど、JavaScriptの基盤に組み込まれつつある機能です。

IE 11が未対応という問題がありますが、Promiseだけであれば比較的容易にPolyfillできますし、「Promiseにして書きやすい」のであれば積極的に使っていっていいのではないかと思います。

投稿2018/05/17 00:24

編集2018/05/17 04:22
maisumakun

総合スコア145121

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

umauman

2018/05/17 05:51

ご回答ありがとうございます。 新しい記述でここまでスッキリ書けるのですね。 babelで変換してみたところとても長いコードになりました。 ただ変換しただけでは動作しないのですが、Polyfillを利用すれば使えるようになるということですね。 ここまで簡潔に書けるのならばはやく素の状態でIEも対応してくれれば本当に嬉しいのですが。
maisumakun

2018/05/17 05:56

残念ながら、IEはもはや新バージョンが出ませんので、諦めるしかなさそうです。
umauman

2018/05/17 06:02

確かにそうでした。恥ずかしいッ!!edgeだと動いてくれますね。
guest

0

未検証ですが、参考までに。

JavaScript

1setTimeout(function handleTimeout (index, data) { 2 console.log(data[index][0]); 3 index = ++index % data.length; 4 setTimeout(handleTimeout, data[index][1], index, data); 5}, 1000, 0, [['コーヒーを注文する', 1000], ['支払いをする', 2000], ['飲む', 5000]]);

検索しているとこのような処理に「Promise」を使用することをすすめている記事が多く、

お勧めされる理由は読みましたか。
現在の要件を踏まえて、他の方法とPromiseを比較検討した上でPromiseが最良と判断していますか。
少なくとも、「新しい」という理由だけで Promise を選択するのは間違っていると私は思います。

私はこの条件であれば、再帰関数を選択します。

Re: umauman さん

投稿2018/05/17 03:53

think49

総合スコア18156

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

umauman

2018/05/17 06:06

ご回答ありがとうございます。 >お勧めされる理由は読みましたか。 >現在の要件を踏まえて、他の方法とPromiseを比較検討した上でPromiseが最良と判断していますか。 「新しいから」や「最良だと思ったから」というよりは、悲しいことにそもそもどのように記述していいかがわからず、一旦、Promiseで記述をしてみたといったところです。(とりあえずでも動くソースがあった方が回答してただきやすいのかなと思いまして) もちろんPromiseを勉強してみたいというのもありましたが、逆に話をややこしくしてしまったかもしれません。 現在のスキルでは下記のように階層で書いていく方法しか持ち合わせておらず、調べると「お勧めされる理由」としてはこんな読みづらい記述をするならば「Promise」で見通し良く書きましょうといった内容が多く見受けられました。 ``` setTimeout(function)… setTimeout(function)… setTimeout(function)… ``` ただ、質問の趣旨としては「知りたいこと」項目の -「複数の関数を時間差で実行してさらに繰り返す方法」の良い書き方を知りたい -「Promiseを選択するのは良いのか?」 といったものなので、ご回答のような内容を待っていました。 いただいたソースを参考に記述をしてみました。(変なソースじゃないですかね..) これだと入れ子にならずに済みますね。大変勉強になりました。 ```JavaScript setTimeout(function handleTimeout (index, data) { data[index][0](); index = ++index % data.length; setTimeout(handleTimeout, data[index][1], index, data); }, 1000, 0, [[orderDrink, 1000], [payBill, 2000], [drinkCoffee, 5000]]); function orderDrink() { console.log('コーヒーを注文する'); } function payBill() { console.log('支払いをする'); } function drinkCoffee() { console.log('飲む'); } ```
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問