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

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

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

ECMAScriptとは、JavaScript類の標準を定めるために作られたスクリプト言語です。

Node.js

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

Redis

Redisは、オープンソースのkey-valueデータストアで、NoSQLに分類されます。すべてのデータをメモリ上に保存するため、処理が極めて高速です。

JavaScript

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

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Q&A

解決済

2回答

12667閲覧

js array forEach内で走る非同期処理の終了タイミングをひろう方法について

bleurouge

総合スコア161

ECMAScript

ECMAScriptとは、JavaScript類の標準を定めるために作られたスクリプト言語です。

Node.js

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

Redis

Redisは、オープンソースのkey-valueデータストアで、NoSQLに分類されます。すべてのデータをメモリ上に保存するため、処理が極めて高速です。

JavaScript

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

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

0グッド

1クリップ

投稿2016/04/15 10:38

編集2016/04/15 15:53

配列をforEachでまわし、内側で走る非同期処理がすべて終了したタイミングをひろい、次の処理に続けたい場合どのような方法があるでしょうか?node.js で keyの配列をforEachでまわし、内側でredis-client.get()を利用するケースを想定しています。

下記の方法で目的の動作はできているのですが、何かスマートじゃないような...
そもそも、forEach?また、まだ利用できていないのですが、generatorを使って次の処理をsleepしておく方法?などもありそうな気がしています。他に良い方法があれば教えていただければ幸いです。よろしくお願いします。

javascript

1var keysArr = ['a','b','c','d','e','f','g','h','i','j','k','l']; 2var forEachWithAsync = function(arr) { 3 return new Promise(function(resolve) { 4 var cnt = 0; 5 console.log('length: ' + arr.length); 6 arr.forEach(function(val, idx, arr) { 7 // node_redis client.get(key, function(err, val) {...}); を使うケースを想定 8 setTimeout(function() { 9 ++cnt; 10 console.log(cnt + ' async log: ' + val); 11 if(cnt === arr.length) { resolve(); } 12 }, Math.round(Math.random()*10000)); 13 }); 14 }); 15} 16 17forEachWithAsync(keysArr) 18.then(function() { console.log('end'); });

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

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

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

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

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

guest

回答2

0

ベストアンサー

これの出番です.

せっかくなのでES6の機能ふんだんに使って書きます.
(Node.jsということなので,特にブラウザの互換性のような問題も気にしなくていいと思います)

JavaScript

1'use strict'; 2 3function sleep(ms) { 4 return new Promise(resolve => setTimeout(resolve, ms)); 5} 6 7const keys = ['a','b','c','d','e','f','g','h','i','j','k','l']; 8 9Promise.all(keys.map( 10 value => sleep(Math.round(Math.random()*10000)).then(() => console.log('done: ' + value)) 11)).then( 12 () => console.log('[all done]'), // すべてresolveされた 13 e => console.error(e) // ひとつでもrejectされた,または例外がスローされた 14);

co+Generatorを使うならもう少し見やすくかけますね.

JavaScript

1'use strict'; 2 3const co = require('co'); 4 5function sleep(ms) { 6 return new Promise(resolve => setTimeout(resolve, ms)); 7} 8 9const keys = ['a','b','c','d','e','f','g','h','i','j','k','l']; 10 11co(function* () { 12 yield keys.map(function* (value) { 13 yield sleep(Math.round(Math.random()*10000)); 14 console.log('done: ' + value); 15 }); 16 console.log('[all done]'); 17}).catch(e => console.error(e));

Babelでコンパイルする前提でasync/awaitを使うなら

JavaScript

1'use strict'; 2 3function sleep(ms) { 4 return new Promise(resolve => setTimeout(resolve, ms)); 5} 6 7const keys = ['a','b','c','d','e','f','g','h','i','j','k','l']; 8 9(async () => { 10 await Promise.all(keys.map(async value => { 11 await sleep(Math.round(Math.random()*10000)); 12 console.log('done: ' + value); 13 })); 14 console.log('[all done]'); 15})().catch(e => console.error(e)); 16 17/* 18 19coを使うけどよりPromiseやasync/awaitとの親和性を高くして書く場合もこのように 20 21co(function* () { 22 yield Promise.all(keys.map(value => co(function* () { 23 yield sleep(Math.round(Math.random()*10000)); 24 console.log('done: ' + value); 25 }))); 26 console.log('[all done]'); 27}).catch(e => console.error(e)); 28 29*/

投稿2016/04/15 11:42

編集2016/04/15 12:24
mpyw

総合スコア5223

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

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

think49

2016/04/15 12:16

編集履歴が Array(7).fill('a') なのが地味に気になります…。
mpyw

2016/04/15 12:28

(ごめんなさいGitのコミットメッセージも雑にするタイプなので)
bleurouge

2016/04/15 15:47

数パターンも記載いただきありがとうございました!このような関数の組み合わせで目的の処理を作れるところがjavascriptの魅力ですね。mapでpromiseな配列要素を作り直すのは実行速度的には気になるところですが、きっとループ内で非同期処理が発生するケースはI/Oでしょうから杞憂なのでしょう。Promiseとarray.xxxxとの組み合わせで何かできるケースは他にもありそうですが、なかなか発想が追いつかない状態です。参考にさせていただきます!
guest

0

個人的には setTimeout() だけで完結させる方法をとります。

setTimeout

JavaScript

1'use strict'; 2setTimeout(function timeout (array, i, resolve) { 3 console.log(i, array[i]); 4 5 if (++i < array.length) { 6 setTimeout(timeout, Math.round(Math.random()*10000), array, i, resolve); 7 } else { 8 resolve(array); 9 } 10}, Math.round(Math.random()*10000), ['a','b','c','d','e','f','g','h','i','j','k','l'], 0, function resolve (array) { console.log('finished!'); });

setTimeout Polyfill (for IE9-)

setTimeout の第3引数は IE9- 未対応なので Polyfill を適用させます。

Re: bleurouge さん

投稿2016/04/15 11:58

think49

総合スコア18156

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

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

bleurouge

2016/04/15 15:50

参考になるご回答ありがとうございました!並行して走り出さない点がPromise.all()+mapとの違いでしょうが、ケースによっては再帰で配列要素を参照していく方法がよさそうですね。しかし、setTimeoutの再帰はなかなか見応えがw
think49

2016/04/16 11:47 編集

> 並行して走り出さない点がPromise.all()+mapとの違いでしょうが Promise は並行して走りだしませんよ。 Promise はコールバック関数による連続処理を扱いやすくするためのインターフェースです。非同期処理を並列処理してくれる魔法の機能ではありませんので…。 試しにPromiseでコードを書いたらCertaiNさんの書いたコードとほぼ同じものが出来ました。別々の関数を順次処理していくのには便利ですが、同じ関数を連続処理するなら再帰処理の方がスマートだと考えています。
bleurouge

2016/04/16 12:58

ありがとうございます。非同期処理が走り始める様子が、並行して走り始めるようにみえるようですね。 ▼並列処理と、どっかで見た記憶が訂正されていました http://ja.stackoverflow.com/questions/10363/ よくよく考えてみると、promise.all([])のかっこ内は配列で、個々の配列要素を一度に操作する術があれば、そもそもforEachなんていらなくなりますね。
think49

2016/04/16 13:18

質問文のforEachに関してはsetTimeoutを10回連続処理した後にconsole.log('end')を呼び出しています。 setTimeoutは1回目のタイマー処理前に2回目のタイマー処理が来てしまった場合に1回目のタイマー処理が終わるまで待ち、1回目のタイマー処理直後に2回目のタイマー処理が発生します。 従って、順序性は失われませんが、インターバルをランダムにする機能が期待通りに動作していません。 forEachは同期処理を順次実行するのには有効ですが、非同期処理を順次実行するならコールバック関数を正しく実装する必要があります。 コールバック処理を再帰処理にするか、Promiseにするかは状況によりますが…。
bleurouge

2016/04/17 12:15 編集

setTimeoutの件ですが、以下私の質問に記載のコードについてコメントいただいた内容かと思います。 >setTimeoutは1回目のタイマー処理前に2回目のタイマー処理が来てしまった場合に1回目のタイマー処理が終わるまで待ち、1回目のタイマー処理直後に2回目のタイマー処理が発生します。従って、順序性は失われませんが、インターバルをランダムにする機能が期待通りに動作していません。 これは私の認識で間違っていたら訂正いただきたいのですが、 1. forEachのループのたび、setTimeoutはイベントループに、第一引数で指定した関数を第二引数指定の時間「Math.round(Math.random()*10000)」とともに登録 2. イベントループのたび、登録した時間が経過しているかチェックされる 3. イベントループ上で指定時間「Math.round(Math.random()*10000)」経過しているなら、登録した関数が実行される つまり、イベントループに登録した時点で、すでにsetTimeoutの実行順序は関係なくなり、イベントループ上に登録された実行までの指定時間が実行順序を左右する。 setTimeoutが非同期処理といわれる点でもそのような認識でした。 その場合、非同期処理を行うsetTimeout関数の第二引数がMath.round(Math.random()*10000)ならば、イベントループ上で他にブロックする処理が入らない限り、おおよそ10秒以内にすべてのconsole.logを表示する動作になるかと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.51%

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

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

質問する

関連した質問