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

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

詳細はこちら
Node.js

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

JavaScript

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

非同期処理

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

Q&A

解決済

2回答

1216閲覧

【非同期処理】処理の順番が思い通りになりません。

ryota002

総合スコア20

Node.js

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

JavaScript

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

非同期処理

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

0グッド

0クリップ

投稿2021/03/24 00:48

前提・実現したいこと

javascriptでTwitter管理システムを作っています。
投稿の機能を実装中に以下の問題が発生しました

発生している問題・エラーメッセージ

scheduledPost関数ないの全ての処理が終わってから、returnで結果を返したいが、処理が終わる前に、returnが実行されている。原因は、「executionCheckedProcessingsData.forEach」の部分だと考えています。

呼び出した関数から { success: [], fail: [ '22時08分' ], error: [] } error 19時22分 [ { code: 187, message: 'Status is a duplicate.' } ] error 22時15分 [ { code: 187, message: 'Status is a duplicate.' } ] error 19時40分 [ { code: 187, message: 'Status is a duplicate.' } ] error 19時11分 [ { code: 187, message: 'Status is a duplicate.' } ] error 19時30分 [ { code: 187, message: 'Status is a duplicate.' } ] error 22時05分 [ { code: 187, message: 'Status is a duplicate.' } ] error 00時00分 [ { code: 187, message: 'Status is a duplicate.' } ] error 22時16分 [ { code: 187, message: 'Status is a duplicate.' } ]

該当のソースコード

scheduledPost.js

js

1const scheduledPost = async (req) => { 2 // 【質問】外部モジュールのimportは、自作関数の中に書くか、外に書くか?(2021/03/23) 3 const tweet = require("../mainFunctions/tweet"); 4 const executionCheck = require("./executionCheck"); 5 6 const executionCheckedProcessingsData = await executionCheck(req); 7 8 const result = { 9 success: [], 10 fail: [], 11 error: [], 12 }; 13 14 executionCheckedProcessingsData.forEach(async (data) => { 15 if (data.execution) { 16 await tweet(data.tweet) 17 .then(() => { 18 console.log("success", data.processingName); 19 result.success.push(data.processingName); 20 }) 21 .catch((error) => { 22 console.log("error", data.processingName, error); 23 result.error.push(data.processingName, error); 24 }); 25 } else { 26 result.fail.push(data.processingName); 27 } 28 }); 29 return result; 30}; 31 32scheduledPost("hoge").then((result) => { 33 console.log("呼び出した関数から", result); 34}); 35 36module.exports = scheduledPost; 37

tweet.js

js

1const Twitter = require("twitter"); 2const oAuth = require("../keys/authKeys.json"); 3 4const tweet = async (postContent) => { 5 const client = new Twitter(oAuth); 6 7 const result = await client.post("statuses/update", { status: postContent }); 8 9 return result; 10}; 11 12module.exports = tweet; 13

executionCheck.js

js

1const executionCheck = async (req) => { 2 const getJapanTime = require("../otherFunctions/getJapanTime"); 3 const fetchProcessingsData = require("../mainFunctions/fetchProcessingsData"); 4 5 const JapanTime = getJapanTime(); 6 const processingsData = await fetchProcessingsData(); 7 8 const executionCheckedProcessingsData = await processingsData.map((data) => { 9 if (req) { 10 data["execution"] = data.switch === "ON"; 11 return data; 12 } else { 13 data["execution"] = data.switch === "ON" && data.startTime === JapanTime; 14 return data; 15 } 16 }); 17 18 return executionCheckedProcessingsData; 19}; 20 21module.exports = executionCheck; 22

試したこと

・非同期処理の問題だと思い、aysnc/awaitやthenメソッドなどを使ってみましたがうまくいきませんでした。

補足情報(FW/ツールのバージョンなど)

node.js v12
twitter 1.7.1

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

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

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

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

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

int32_t

2021/03/24 01:01

配列 executionCheckedProcessingsData に対する twee() 処理は、配列の全要素を並列に処理したいですか、配列の要素を1つずつ処理したいですか?
ryota002

2021/03/24 01:09

全てに対して処理が行われ、結果がresultに格納されれば、どちらでも構いません。 並列の方が処理速度が早いですか? また、並列か1つずつで書き方はどのように変わりますか?
guest

回答2

0

JS上で非同期処理をどの順番でやっているかは厳密に決められています。
いずれですが一度きっちり学習するべきですが、簡単に説明しておきます。

JSにはイベント置き場というものがあり、
「達成条件・実行して欲しい処理を関数で包んだもの」を1セットでイベント登録を行います。
イベント登録が行われた際、JSは「わかった、後で達成されたら確認して実行しておくよ」とスルーします。

js

1console.log(1); 2setTimeout( 3 () => console.log(2), 4 0 5); 6console.log(3);

setTimeout(fn, time)はtimeミリ秒待ってから実行するイベント登録です。
0ミリ秒なのでその場で即条件を達成していますが、この関数は何時実行されるのでしょうか?
正解はイベント登録だけ行って関数の実行をせず一度下まで通しで実行する。

その後にイベントループという達成したイベントを巡回して探す処理がJSの裏側で起こり、
0ミリ秒経過して達成している関数を拾い上げてconsole.log(2)を実行します。

従って出力結果は必ず1 -> 3 -> 2の順番になります。


今回のケースはPromiseを使っていますが、
Promiseもイベント登録です。

JSの非同期処理のクソな部分として
コールバック地獄でコードが無限にネストするというのが挙げられますが、

Promiseはあらゆるオブジェクト指向のテクニックを使って、
コールバック地獄をメソッドチェーンで記述して解決出来るようにしただけの代物です。
別にPromiseを使ったから非同期処理から解き放たれる訳ではなく、
JSのイベント登録→イベント達成→関数実行という流れに逆らっては居ません。

async/await構文はいわゆる糖衣構文で、
Promiseの.then(fn)を使っているだけです。
イベント登録の先で拾うよう指示を出しているだけです。


以上を踏まえてコードを添削します。

scheduledPost.js

js

1// 外部モジュールのimportは、どうしても実現出来ない場合を除いて常にファイルの先頭で行う 2const tweet = require("../mainFunctions/tweet"); 3const executionCheck = require("./executionCheck"); 4 5const scheduledPost = async req => { 6 const executionCheckedProcessingsData = await executionCheck(req); 7 8 const result = { 9 success: [], 10 fail: [], 11 error: [], 12 }; 13 14 // forEachはPromiseの束縛が切れて全てを即時実行するので、for...ofを使う 15 for (const {execution, tweet: text, processingName} of executionCheckedProcessingsData) { 16 // ガード節でネストを減らす 17 if (!execution) { 18 result.fail.push(processingName); 19 continue; 20 } 21 try { 22 await tweet(text); 23 console.log("success", processingName); 24 result.success.push(processingName); 25 } catch (e) { 26 console.error("error", processingName, e); 27 result.error.push(processingName, e); 28 } 29 } 30 31 return result; 32}; 33 34// TODO:テストコード?後で削除する 35scheduledPost("hoge").then(result => { 36 console.log("呼び出した関数から", result); 37}); 38 39module.exports = scheduledPost;

executionCheck.js

js

1const getJapanTime = require("../otherFunctions/getJapanTime"); 2const fetchProcessingsData = require("../mainFunctions/fetchProcessingsData"); 3 4// 変数は1個でも減らしたほうが可読性が高くなる 5module.exports = async req => { 6 const JapanTime = getJapanTime(); 7 const processingsData = await fetchProcessingsData(); 8 9 return processingsData.map(data => { 10 ...data, 11 execution: req 12 ? data.switch === "ON" 13 : data.switch === "ON" && data.startTime === JapanTime 14 }); 15};

投稿2021/03/24 03:19

編集2021/03/24 03:21
miyabi-sun

総合スコア21203

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

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

ryota002

2021/03/24 03:35 編集

こんなにご丁寧に大変ありがとうございます。 ページの更新前に、他の方の内容で処理がうまくいったので、貴方の回答に気づく前にベストアンサーを決めてしまいました。 回答いただいた内容は、とても勉強になる内容でした。 現在の自分には、少し難しく、深く勉強する必要があるなと感じました。 回答いただいた内容を参考に勉強を進めていきます。 フォローさせていただいたので、勉強を進めていく中で躓くところがありましたら、また質問させてください。 ご丁寧に、ご回答いただきありがとうございました。
guest

0

ベストアンサー

forEachの中身をasyncで書いても、外側のasyncとは関連せずに進んでしまいます。

「非同期処理をすべて待ちたい」なら、mapPromiseを作った上で、await Promise.allとしましょう(参考)。

投稿2021/03/24 01:11

maisumakun

総合スコア145975

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

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

maisumakun

2021/03/24 01:12

これは「全部並列で実行する」形となります。
ryota002

2021/03/24 03:27

上手くいきました! ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問