🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
JavaScript

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

非同期処理

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

Q&A

解決済

1回答

574閲覧

for文内にsetTimeoutがある関数に対して、promise(async/await)を用いて非同期処理をスマートに記述したい。

take-t.t.

総合スコア360

JavaScript

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

非同期処理

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

0グッド

1クリップ

投稿2019/10/12 14:38

いつもお世話になっております、今実現したいことはタイトル通りです。
下記のコードを実行すると、当然先に「Done」が出力されてしまいます。
そこでaddTypingMovement内でPromiseを返すようにして、thenで繋げてfinishTitleCallを実行しようと思っているのですが、その場合どのようにresolveを記述すればよいのか分かりません。
一応最終ループ時(iword.length - 1の時)にさらに200msずらしてresolveすれば期待通りの動作にはなるのですが、それではPromiseを使う意味がほぼ無いですし、分からないなりにベストプラクティスでは無いだろうという事を薄々感じています。

自分なりに調べてみたのですが、こういったケースに参考になる記事等が見つけられず、手詰まりになっています。
そもそもPromiseに対する理解があまり深くなく、的外れな質問をしてしまっていたら申し訳ありません。

ですが宜しければ、お力添えをよろしくお願いいたします。

サンプル → https://jsfiddle.net/rdvy39b8/

html

1<p id="title"></p> 2<p id="result"></p>

JavaScript

1const addTypingMovement = (word, target) => { 2 const wordArray = [...word]; 3 const printTarget = document.querySelector(target); 4 for(let i = 0; i < wordArray.length; i++) { 5 setTimeout(() => { 6 printTarget.textContent = printTarget.textContent + wordArray[i]; 7 }, i * 200) 8 } 9} 10 11const finishTitleCall = () => { 12 document.querySelector('#result').textContent = 'Done.' 13} 14 15addTypingMovement('Hello World.', '#typing'); 16finishTitleCall();

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

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

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

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

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

guest

回答1

0

ベストアンサー

こんにちは

この回答では3つのコードを挙げます。ご質問のタイトルに、

for文内にsetTimeoutがある関数に対して、・・・

とあったので、 まずご質問のコードにある forループを生かしたコードを回答します。その後、for文を使わないコードを2つ挙げます。

1. forループで、各文字を追加するPromiseの配列を作る。

各文字を追加していく処理をひとつのPromiseにして、それらがすべてresolve されたら、Doneを表示するという前後関係を担保するために、 Promise.all を使えばよいかと思います。

以下、ご質問に挙げられているコードに、上記の趣旨の追加をしたものです。

javascript

1const addTypingMovement = (word, target) => { 2 const promises = []; 3 const wordArray = [...word]; 4 const printTarget = document.querySelector(target); 5 6 for(let i = 0; i < wordArray.length; i++) { 7 const p = new Promise(resolve => { 8 setTimeout(() => { 9 printTarget.textContent = printTarget.textContent + wordArray[i]; 10 resolve(); 11 }, i * 200) 12 }); 13 promises.push(p); 14 } 15 16 return promises; 17} 18 19const finishTitleCall = () => { 20 document.querySelector('#result').textContent = 'Done.' 21} 22 23Promise.all(addTypingMovement('Hello World.', '#typing')).then(finishTitleCall);

以下は、ご質問に挙げられている jsFiddle をFork して、上記の修正版にしたものです。

2. forの替わりにmapを、前後関係の制御にasync/awaitを使用

上記 1. のコードで、Promiseの配列を作るところの for文を map を使って書き換え、 すべての文字が target に追加されたら、Done を表示するために async, await を使った例が以下です。

javascript

1const addTypingMovement = (word, target) => { 2 3 const printTarget = document.querySelector(target); 4 5 return [...word].map((char, i) => new Promise( 6 resolve => { 7 setTimeout(() => { 8 printTarget.textContent += char; 9 resolve(); 10 }, i * 200); 11 }) 12 ); 13 14} 15 16const finishTitleCall = () => { 17 document.querySelector('#result').textContent = 'Done.'; 18} 19 20(async () => { 21 await Promise.all(addTypingMovement('Hello World.', '#typing')); 22 finishTitleCall(); 23})(); 24

3. 最後の文字まで表示するのを一つのPromiseにする。

上記の 1. と 2. とは異なり、「200ミリ秒間隔でひと文字ずつ、最後の文字まで表示する」という処理をひとつの Promise にすることもできます。以下のそのコード例です。

javascript

1const addTypingMovement = (word, target) => new Promise( 2 resolve => { 3 const printTarget = document.querySelector(target); 4 let i = 0; 5 const intervalId = setInterval( 6 () => { 7 if (i < word.length) { 8 printTarget.textContent = word.substring(0, ++ i); 9 } else { 10 clearInterval(intervalId); 11 resolve('Done.'); 12 } 13 }, 200); 14 } 15); 16 17const finishTitleCall = (message) => { 18 document.querySelector('#result').textContent = message; 19} 20 21(async () => { 22 const message = await addTypingMovement('Hello World.', '#typing'); 23 finishTitleCall(message); 24})();

   

上記では、

  • setTimeoutの替わりにsetIntervalを使っています。
  • 文字列全体が表示されるまでをひとつの Promise にするので、 Promise.all は使わなくなっています。
  • Done.という文字列を resolveの引数で返させるようにしました。

以上、参考になれば幸いです。

投稿2019/10/12 16:39

編集2019/10/12 19:52
jun68ykt

総合スコア9058

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

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

take-t.t.

2019/10/12 20:41 編集

ご回答ありがとうございます、複数の方法も教えていただき恐縮です。 自分では思いつかない方法ばかりでとても勉強になりました。 ただ自分の理解力が足りず、2つ目のasync/awaitを用いた例のコードに不明な点があるため少し質問させて下さい。 まずaddTypingMovementは、word.length個のPromiseオブジェクトを値に持つ配列をreturnする関数であり、 各Promiseオブジェクトのコールバックに、配列のindex * 200ms後に処理を実行するタイマーが格納されている、という事でしょうか? 私の感覚では、飽くまでPromiseを返す関数というより、mapにより配列を返す関数を実行しているように見え、どのように動いているのかイマイチ理解できませんでした。 各Promiseオブジェクトのタイマーはどのタイミングで呼ばれているのでしょうか?
jun68ykt

2019/10/13 04:57

コメントありがとうございます。 > 2つ目のasync/awaitを用いた例のコード へのご質問に回答します。 > まずaddTypingMovementは、word.length個のPromiseオブジェクトを値に持つ配列をreturnする > 関数であり、各Promiseオブジェクトのコールバックに、配列のindex * 200ms後に処理を > 実行するタイマーが格納されている、という事でしょうか? はい。そのご理解で合っています。 回答に書いた3つのコードの中で、関数addTypingMovementが Promiseオブジェクトを返しているのは、3つ目のコードのみです。 > 各Promiseオブジェクトのタイマーはどのタイミングで呼ばれているのでしょうか? setTimeout関数が呼ばれるタイミング(= タイマーがスタートするとき)は、Promiseオブジェクトが new で作成されるときです。それがより明確に分かるコード例を以下に書きました。 https://jsfiddle.net/jun68ykt/xh6wksyc/4/ 上記の例では、各 Promise に渡すコールバックでは、 resolve も reject も受け取っておらず、単に、各文字で1秒ずつ長い時間でタイマーをセットして、タイムアウトしたらその文字を console に出力しています。Promiseの基礎知識として、Promiseオブジェクトには以下の3つの状態 ・Pending --- promiseオブジェクトが作成されたときの状態 ・Fulfilled --- resolve(成功)した時の状態 ・Rejected --- reject(失敗)した時の状態 がありますが、上記の例では、resolveもrejectもしていないので、各Promiseオブジェクトの状態はPendingのままですが、各Promiseのコールバック本体にある setTimeout で設定された関数が、指定の時間後に実行されて、consoleに、1秒ごとに h,e,l,l,o と一文字ずつ表示されるのを確認できると思います。 蛇足ですが、Promise の基礎を押さえるには、以下をお勧め致します。 JavaScript Promiseの本 ( @azu_re さん著 ) https://azu.github.io/promises-book/ まずは上記の  Chapter.1 - Promiseとは何か  Chapter.2 - Promiseの書き方 を読むと、かなり整理できると思います。
take-t.t.

2019/10/13 20:28

ご返信ありがとうございます。 頂いたコードを動かしながらなんとか理解できました。 Promiseに関して適当に理解した気になっていた部分が多々あり、質問の内容とはズレたところでお手数をおかけしてしまい申し訳ありませんでした。 Promiseの本を参考に一から学びなおしてみます。 改めまして、複数回にも渡ってご回答頂き本当にありがとうございました。
jun68ykt

2019/10/13 23:24

どういたしまして。 > 頂いたコードを動かしながらなんとか理解できました。 とのことでよかったです ????
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問