複数の関数を時間差で実行してさらに繰り返す方法
解決済
回答 3
投稿
- 評価
- クリップ 3
- VIEW 2,632
前提・実現したいこと
複数の関数を時間差で実行してそれを繰り返す方法を知りたく思っています。
実行内容
console.logで下記を表示
(1秒後)コーヒーを注文する→(2秒後)支払いをする→(5秒後)飲む→(1秒後)コーヒーを注文する
→(2秒後)支払いをする→(5秒後)飲む・・・エンドレスにこの処理
※本当はもっと色々な処理をさせたいのですが質問しやすいようにシンプルにしました。
検索しているとこのような処理に「Promise」を使用することをすすめている記事が多く、ひとまず自分なりに記述をしてみました。
ただ、これを繰り返し実行する方法がわからずにいます。
知りたいこと
- 表題の「時間差で実行、さらに繰り返し」処理を行うにはどのような記述を行うのが良いのか
Promise
で処理をする選択は良いのか?その場合繰り返しをさせるにはどのように記述するべきか- そもそも私の
Promise
の記述に誤りが無いか、もしくはもっと良い記述があればご指摘いただけると助かります
function orderCoffee() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('コーヒーを注文する');
resolve();
}, 1000);
});
}
function payBill() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('支払いをする');
resolve();
}, 2000);
});
}
function drinkCoffee() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('飲む');
resolve();
}, 5000);
});
}
//下記を繰り返し実行させたい
orderCoffee()
.then(payBill)
.then(drinkCoffee)
どうぞ宜しくお願いします
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+6
Q1. 「時間差で実行、さらに繰り返し」処理を行うには?
とりあえず、Promise
を使う前提で書いて見たコードが以下となります。
Pattern A
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で処理をする選択は良いのか?
質問者さんの前回の質問の場合は、「一定間隔」で複数の(異なる)処理を実行させたいというものだったので、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()
が実行されるようにコードを書いてあげる必要があります。
参考
掲載コードの中でgenerator
、spread syntax
、rest parameters
を使っていますが、詳細やブラウザ対応状況は以下でご確認ください。(英語記事へのリンクになっていますが、リンク先ページで言語切り替えが可能です。)
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+5
async
-await
を使えば、「非同期な無限ループ」も、ものすごくすっきり書けます。
function sleep(time){
return new Promise(resolve => {
setTimeout(resolve, time);
});
}
async function main(){
for(;;) {
await sleep(1000);
console.log('コーヒーを注文する');
await sleep(2000);
console.log('支払いをする');
await sleep(5000);
console.log('飲む');
}
}
main();
なお、IE(11でも非対応)やiOS 10.3未満では非対応なので(Can I use)、そういう環境ではBabelなどで変換することが必要です。
Promise
で処理をする選択は良いのか?その場合繰り返しをさせるにはどのように記述するべきか
Promise
は単なるライブラリの域を超えて、ジェネレーターやasync
-await
といった文法構造とも結びついている(上のコードでもnew Promise
の結果をawait
で「待たせて」います)、fetch
がPromise
で結果を返すなど、JavaScriptの基盤に組み込まれつつある機能です。
IE 11が未対応という問題がありますが、Promise
だけであれば比較的容易にPolyfillできますし、「Promise
にして書きやすい」のであれば積極的に使っていっていいのではないかと思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+4
未検証ですが、参考までに。
setTimeout(function handleTimeout (index, data) {
console.log(data[index][0]);
index = ++index % data.length;
setTimeout(handleTimeout, data[index][1], index, data);
}, 1000, 0, [['コーヒーを注文する', 1000], ['支払いをする', 2000], ['飲む', 5000]]);
検索しているとこのような処理に「Promise」を使用することをすすめている記事が多く、
お勧めされる理由は読みましたか。
現在の要件を踏まえて、他の方法とPromiseを比較検討した上でPromiseが最良と判断していますか。
少なくとも、「新しい」という理由だけで Promise
を選択するのは間違っていると私は思います。
私はこの条件であれば、再帰関数を選択します。
Re: umauman さん
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.20%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2018/05/17 14:58
ジェネレーターのことは初めて知りました。(function*)←一瞬間違いかと思ってしまいました。
調べてみましたが定義をしてループに渡す流れ、そしてyieldは一旦処理を止めるものなのですね。
sleep関数も勉強になりました。他の言語だとずばりこのような関数があるのですね。
前回の質問への回答もありがとうございます。(ちょっと違う処理を間にはさんでくださっていてありがとうございます)
rejectされたときの場合の処理も記述した方がいいことも理解しました。
全体的に恐らく運用管理面についても考慮されているソースをのだろうなという印象を受けました。
とても参考になりそうです。本当にありがとうございました!
2018/05/17 15:23
2018/05/17 22:59
ご回答いただいた内容が色々と調べるのに良いとっかかりになりそうです。
再度、本当にありがとうございました!