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

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

詳細はこちら
Node.js

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

JavaScript

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

非同期処理

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

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

Q&A

解決済

1回答

1697閲覧

JavaScript(Node.js)の非同期通信でリクエストを分割して処理をしたい

tax231

総合スコア14

Node.js

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

JavaScript

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

非同期処理

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

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

0グッド

0クリップ

投稿2020/12/09 04:43

前提・実現したいこと

非同期通信やpromiseについての理解が乏しいので質問させてください。
取得したjanコードを元にkeepaのapiで他サイトの価格を引っ張ってくるツールを制作しようとしています。
元のjanコードの量は場合により1000,2000個のような膨大な数になる可能性があり(ここではdataListX)、keepaのapiではリクエスト上限が100なのでjanコードを100ずつ区切ってapiを叩いて結果を取得して整形..と繰り返していきたいのですが、
ここのコードの26行目以下(await sendData(chunk).then((response)=> { ..の部分)でレスポンスがないまま先に進んでしまいエラーになります。

promise周りのみならず、書き方がまずい部分もあればご教授いただきたいです。
よろしくおねがいします。

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

(node:220) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'length' of undefined (node:220) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1) (node:220) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

該当のソースコード

JavaScript

1const puppeteer = require('puppeteer'); 2const fs = require('fs'); 3const rp = require('request-promise'); 4const $ = require('jQuery'); 5const _ = require( 'lodash' ); 6 7var dataListY = [] 8 9let dataListX = [ 10 ["https://www.netsea.jp/shop/25774/UAG-SFPRO4-BLK?cx_search=score","4988481755953","ビギナー拒否設定のため非公開"], 11 ["https://www.netsea.jp/shop/25774/UAG-SFPRO4-CBT?cx_search=score","4988481755977","ビギナー拒否設定のため非公開"], 12 ["https://www.netsea.jp/shop/25774/CRT-PFNG116W?cx_search=score","4969887294314","ビギナー拒否設定のため非公開"], 13 ... 14]; 15 16 17//リクエストを作成 18let productCodes = []; //#2 100個まで 19for (var i = 0; i < dataListX.length; i++) { 20 productCodes.push(dataListX[i][1]); 21} 22 23async function dataFlow(){ 24 for ( const chunk of _.chunk( productCodes, 100 ) ) { 25 // 100ずつの配列をsendData関数に渡す 26 await sendData(chunk).then((response)=> { 27 if(response){ 28 let products = response.products; //response.products にそれぞれの商品のデータが配列で入ってる 29 console.log(products.length); //=>3 3つリクエストしたのでレスポンスも3つ 30 for(var i=0;i<products.length;i++){ 31 var product = products[i]; 32 let itemDetail = []; 33 if(product.eanList) { 34 itemDetail.push(String(product.eanList)); // EAN(JAN) 35 } else { 36 itemDetail.push(''); 37 } 38 itemDetail.push(product.title); // 商品名 39 if(product.csv[0]) { // Amazon本体の最新の出品価格 40 itemDetail.push(product.csv[0][product.csv[0].length-1]); 41 } else{ 42 itemDetail.push(''); 43 } 44 if(product.csv[1]) { // 新品の最新の出品価格(最安値) 45 itemDetail.push(product.csv[1][product.csv[1].length-1]); 46 } else { 47 itemDetail.push(''); 48 } 49 // console.log('---------------------------------------------------------------------------'); 50 dataListY.push(itemDetail); 51 console.log(dataListY); 52 for(var i=0;i<dataListX.length;i++){ 53 for(var ii=0;ii<dataListY.length;ii++){ 54 let searchItem = dataListY.vlookup(dataListY[ii][0], 0); 55 let pushItem1 = dataListY.vlookup(dataListY[ii][0], 1); 56 let pushItem2 = dataListY.vlookup(dataListY[ii][0], 2); 57 let pushItem3 = dataListY.vlookup(dataListY[ii][0], 3); 58 if (dataListX[i][1] === searchItem) { 59 dataListX[i].push(pushItem1, pushItem2, pushItem3); 60 } 61 } 62 } 63 } 64 } 65 }) 66} 67dataFlow(); 68 69async function sendData(chunk) { 70 const options = { 71 url : 'https://api.keepa.com/product', 72 method : 'POST', 73 form : { 74 key : xxxxxxxxxxxxxx, 75 code : chunk.join(','), 76 domain : '5', 77 }, 78 gzip: true, 79 headers : { 80 'Content-Type':'application/json', 81 'Connection' : 'keep-alive' 82 }, 83 json : true, 84 resolveWithFullResponse: true 85 } 86 const response = rp(options); 87 if(response) return response; 88} 89

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

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

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

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

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

guest

回答1

0

ベストアンサー

パッと見でダメそうな箇所はいくつかありますが、
順番にいってみましょうか。

TypeError: Cannot read property 'length' of undefined

JSではこのエラー文は頻出です。
即何処を直せば良いか理解出来るようになりましょう。

まず、JSはオブジェクト指向言語なので
value.propertyという風にドットプロパティ名という記述で
メソッドやプロパティを呼び出す事ができます。

しかし、JSではプロパティの所持が許されない例外の値が2つ存在し、
アクセスを試みただけでエラーで落ちます。

  • null
  • undefined

当てずっぽうですが、
恐らくこれで間違いないでしょう。

js

1 let products = response.products; //response.products にそれぞれの商品のデータが配列で入ってる 2 console.log(products.length); //=>3 3つリクエストしたのでレスポンスも3つ

responseが何者かはチェックしていますが、
productsというプロパティが何者かをチェックしていません。
つまり、こういう流れでエラーになっているのでしょう。

  • response: 何か入っている
  • response.products: キーが存在しない、undefined
  • response.products.length: undefinedからプロパティ参照を行った為にエラー

なぜこいつが怪しいかという話ですが、
sendData関数の戻り値はかなり怪しいですね。
本当にproductsキーに値が入っている事は保証されるんですか?

デバッグをしながら少しずつ進めていってください。


ではリファクタリング案に関して見ていきましょう。

js

1 for(var i=0;i<dataListX.length;i++){ 2 for(var ii=0;ii<dataListY.length;ii++){ 3 let searchItem = dataListY.vlookup(dataListY[ii][0], 0); 4 let pushItem1 = dataListY.vlookup(dataListY[ii][0], 1); 5 let pushItem2 = dataListY.vlookup(dataListY[ii][0], 2); 6 let pushItem3 = dataListY.vlookup(dataListY[ii][0], 3); 7 if (dataListX[i][1] === searchItem) { 8 dataListX[i].push(pushItem1, pushItem2, pushItem3); 9 } 10 } 11 }

なんでこいつが毎ループで実行されるんですか?
計算量が爆発して死ぬ未来しか見えません。

更にdataListYって何者なんですか?
上の方にvar dataListY = []って書いてますよね?
JavaScriptのArrayが所持しているメソッドvlookupというものはありません。

恐らく大幅に省かれた何かが原因でしょう。
よってこれに関してはノータッチとします。

次。

js

1const rp = require('request-promise'); 2const $ = require('jQuery');

Node.jsにはDOMを扱う機能が入ってないので、
Node.js上でHTMLファイルをパースして情報を抜き出したいならば、
cheerioを使いましょう。

js

1async function dataFlow(){ 2 for ( const chunk of _.chunk( productCodes, 100 ) ) { 3 // 100ずつの配列をsendData関数に渡す 4 await sendData(chunk).then((response)=> { 5 if(response){ 6 let products = response.products; //response.products にそれぞれの商品のデータが配列で入ってる

if文の次の行、字下げ忘れですね。
なのに関数閉じる箇所は綺麗な階段になっている。

推測ですがコピペのしすぎでカッコの開き、閉じの対応が取れてないのでは?
見直してみてください。

そもそも1個のif文で何十行も中括弧({})で包むからネストが深くなって辛いんですよ。
リーダブルコードを読んだ上で、早期returnを採用する等した方が良いでしょう。
for文の中で使うならif (!response) continue;にするとか。

await sendData(chunk).then((response)=> {

これではawaitを使っている意味が1ミリもありません。
awaitにはPromiseオブジェクトを待つという意味があります。

これで不要なthenメソッドの呼び出しや、
関数を包んでの実行を省略するという意味で使います。
上記までを踏まえて書くとこんな感じになります。

js

1async function dataFlow(){ 2 for ( const chunk of _.chunk( productCodes, 100 ) ) { 3 const response = await sendData(chunk); 4 if (!response) continue; 5 let products = response.products; //response.products にそれぞれの商品のデータが配列で入ってる 6 console.log(products.length); //=>3 3つリクエストしたのでレスポンスも3つ 7 8 // 既存の流れ 9 } 10}

まぁまぁ綺麗になってきました。

書き方がまずい部分もあればご教授いただきたいです。

二次元配列はやめましょう。
配列の0番目はURLで、1番目にはIDが入ってて……
みたいな作りは後々忘れてコードの修正で死ぬので絶対に無しです。

貴方はdataListX[i][1]が何かを即答出来ますか?

似たような構造の繰り返しデータになる場合、
オブジェクトの配列が最も適しています。
情報量が多くなる場合は、YAMLファイル等の外部ファイルで管理するのも良い

js

1const dataListX = [ 2 { 3 url: "https://www.netsea.jp/shop/25774/UAG-SFPRO4-BLK?cx_search=score", 4 productCode: "4988481755953", 5 detail: "ビギナー拒否設定のため非公開", 6 }, 7 { 8 url: "https://www.netsea.jp/shop/25774/UAG-SFPRO4-CBT?cx_search=score", 9 productCode: "4988481755977", 10 detail: "ビギナー拒否設定のため非公開", 11 }, 12 { 13 url: "https://www.netsea.jp/shop/25774/CRT-PFNG116W?cx_search=score", 14 productCode: "4969887294314", 15 detail: "ビギナー拒否設定のため非公開", 16 }, 17 ... 18];

こう変更した威力は次項でわかります。

for (var i = 0; i <

なんで他の箇所ではfor...of使ってるのに、
いきなりforに戻るんですか?
どちらかに統一しましょう。

forかfor...ofのどちらを使うかの判断基準ですが、
iという添字自体に用事はなく、配列の中身の方に用事があるんですよね?
こういうiみたいな変数を状態変数と呼び、文章を読み書きする上で理解の妨げになるノイズです。
減らした方が偉いので減らしましょう。

配列やコレクションを元にループを回したい場合、
出来る限りfor...ofを利用しましょう。
前述の項目と合わせてこうなります。

js

1const productCodes = []; // pushはconstでも可能 2for (const {productCode} of dataListX) { 3 productCodes.push(productCode); 4} 5 6// 因みにArray.prototype.mapを使うと1行で書ける 7const productCodes = dataListX.map(it => it.productCode);

しかし、URLがそれぞれ異なるので
これを100個ひとまとめにしても無駄なのでは?


dataListYも同様ですね。
という訳で、上記を踏まえてざっくり書き直すとこうなります。

js

1const dataListX = [ 2 {url: "https://www.netsea.jp/shop/25774/UAG-SFPRO4-BLK?cx_search=score", productCode: "4988481755953", detail: "ビギナー拒否設定のため非公開"}, 3 {url: "https://www.netsea.jp/shop/25774/UAG-SFPRO4-CBT?cx_search=score", productCode: "4988481755977", detail: "ビギナー拒否設定のため非公開"}, 4 {url: "https://www.netsea.jp/shop/25774/CRT-PFNG116W?cx_search=score", productCode: "4969887294314", detail: "ビギナー拒否設定のため非公開"}, 5 ... 6]; 7const productCodes = dataListX.map(it => it.productCode); 8 9async function dataFlow() { 10 // 一度に動作させるfor文の粒度は小さくした方が良い 11 // productsをまず受け取って配列を作る 12 const products = []; 13 for (const chunk of _.chunk(productCodes, 100)) { 14 // 100ずつの配列をsendData関数に渡す 15 const response = await sendData(chunk); 16 if (!response) continue; // なんで結果が帰ってこないんだ、例外とかエラーとして通知する必要ありそう 17 for (const p of response.products) { 18 products.push(p); 19 } 20 } 21 22 // 変数は使う直前で宣言すべし 23 const dataListY = []; 24 for (const product of products) { 25 dataListY.push({ 26 title: product.title, 27 ean: product.eanList ? String(product.eanList) : "", 28 amznPrice: product.csv[0] ? _.last(product.csv[0]) : "", 29 newPrice: product.csv[1] ? _.last(product.csv[1]) : "", 30 }); 31 } 32 33 // データリストの突き合わせは省略 34}

投稿2020/12/09 09:34

miyabi-sun

総合スコア21203

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

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

tax231

2020/12/17 15:12

ご返信が大変遅れました。 読み込んで修正とリファクタリングを行い、徐々に解決していきました。 大変勉強になりました。 TypeError: Cannot read property 'length' of undefined こちらはsendDataのoptionでいらぬ設定をしていたことで起きたものでした。
miyabi-sun

2020/12/18 02:48

お疲れ様です、原因特定まで出来て良かったです。 よく頑張りましたね。 オタクの超早口みたいな豪速球な回答だったので、 飲み込んでコードに反映するまでにかなり時間が掛かるだろうなとは思ってました。 余談ですが、ソースコードの書式の綺麗さを担保したい場合、 eslintを使ってみるのも良いでしょう。 「eslint vscode」という風に利用しているエディタとセットで検索すると、 エディタでJSファイルを開いた時に裏で解析しながら教えてくれるようになります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問