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

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

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

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Node.js

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

JavaScript

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

Slack

Slackは、Tiny Speckという企業からリリースされたコミュニケーションツールです。GoogleDriveやGitHubなど、さまざまな外部サービスと連携することができます。

Q&A

解決済

2回答

5252閲覧

node.jsでapiを叩いた結果をまとめてjsonで扱う

sasagar

総合スコア15

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Node.js

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

JavaScript

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

Slack

Slackは、Tiny Speckという企業からリリースされたコミュニケーションツールです。GoogleDriveやGitHubなど、さまざまな外部サービスと連携することができます。

1グッド

1クリップ

投稿2017/02/13 05:58

編集2017/02/13 06:10

###前提・実現したいこと
Slack Web Apiをslack-nodeを利用してnode.jsを使って叩いてます。
最終的に複数のチームに跨がり投稿をチェックし、最終投稿を全て取得するようなモノを作ろうとしています。

とっかかりと言うことで、複数のtokenをforEachで回し、受け取ったjsonを配列として(?)結合したいと考えています。

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

slack-node自体は問題なく動作しているのですが、受け取った内容をうまく結合できずにおり、
最初のところで躓いています。
forEachの中では問題なくconsole.logで出力出来ているのですが、forEachを出てしまうと、下記コード内jsonTeamが出力出来なくなってしまっています。
次の処理に渡すためにも、複数回(今回の例は5回)分のデータをまとめた状態のjsonを作りたいです。

###該当のソースコード

javascript

1var http = require('http'); 2var https = require('https'); 3 4var Slack = require('slack-node'); 5 6var apiTokens = []; 7apiTokens.push('xoxp-<token1>'); 8apiTokens.push('xoxp-<token2>'); 9apiTokens.push('xoxp-<token3>'); 10apiTokens.push('xoxp-<token4>'); 11apiTokens.push('xoxp-<token5>'); 12 13var jsonTeam = []; 14 15console.log('start'); 16 17function one() { 18 return new Promise(function(resolve) { 19 apiTokens.forEach (function (apiToken) { 20 var slack = new Slack(apiToken); 21 slack.api("team.info", function(err, response) { 22 delete(response.team.email_domain); 23 var icon = response.team.icon.image_original; 24 delete(response.team.icon); 25 response['team']['icon'] = icon; 26 jsonTeam.push(response.team); 27 console.log(jsonTeam); 28 // ここならjsonTeamが出力できた。 29 }); 30 }); 31 resolve(); 32 }); 33} 34 35one().then(function() { 36 console.log(jsonTeam); // ここで、jsonTeamが出力出来ない。 37}); 38 39console.log('end');

###試したこと
あまりにいろいろと試してしまい、何をやったか整理できておりませんが、
Promiseについて調べて、スタンダードに書いてみようかと思ったりしたところです。

kong👍を押しています

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

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

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

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

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

guest

回答2

0

ベストアンサー

まず、間違いを指摘すると

js

1apiTokens.forEach(function (apiToken) { 2//... 3}); 4resolve();

forEachは同期処理ですので、すぐにresolve()が実行されてしまいます
resolve()は非同期で実行される関数の中に書かなければなりません。
つまり、slack.api()のコールバック関数内に書きます。

js

1slack.api("team.info", function(err, response) { 2 // 処理 3 4 resolve(); 5});

そのほかにも問題があります。
それは、forEachでslack.api()を実行しているということです。
これだと、配列の数分一気にリクエストを投げることになるので負荷がかかり、場合によってはタイムアウトやエラーになってしまう可能性も出てきます。
ですので、リクエストを投げてレスポンスを受け取ってから次のリクエストを投げるようにしないといけません。

ということで、Promiseを使用したもので修正したのが以下のコードとなりますが、こっちも頭が煮えちゃうかな。
one関数を便宜上getTermInfoという関数名に変更してます。
getTeamInfo関数にapiTokenをそれぞれバインドした関数の配列を用意し、その配列の最後に
console.log(jsonTeam);
を行う関数を追加します。
用意した配列を、Array.reduce()によりひとつづつ実行していきます。
reduce()はちと混乱しちゃうかもですが、ようは

js

1getTeamInfo('<token1>').then(getTeamInfo('<token2>')).then(() => console.log(jsonTerm));

を行っています。Array.reduce()を使うことで配列に対応することが可能となります。

js

1var Slack = require('slack-node'); 2var apiTokens = [ 3 'xoxp-<token1>', 4 'xoxp-<token2>' 5]; 6 7var jsonTeam = []; 8function getTeamInfo(apiToken) { 9 return new Promise(function (resolve) { 10 var slack = new Slack(apiToken); 11 slack.api("team.info", function (err, response) { 12 if (!response.error) { 13 delete (response.team.email_domain); 14 var icon = response.team.icon.image_original; 15 delete (response.team.icon); 16 response['team']['icon'] = icon; 17 jsonTeam.push(response.team); 18 //console.log(jsonTeam); 19 // ここならjsonTeamが出力できた。 20 } 21 resolve(); 22 }); 23 }); 24} 25 26const arr = []; 27apiTokens.forEach((apiToken) => arr.push(getTeamInfo.bind(null, apiToken))); 28arr.push(function () { 29 console.log(jsonTeam); 30}); 31arr.reduce((r, p) => { 32 return r.then(p) 33}, Promise.resolve());

投稿2017/02/13 07:46

編集2017/02/13 07:53
turbgraphics200

総合スコア4267

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

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

sasagar

2017/02/13 07:58

ありがとうございます。 なるほどforEachを利用してもこの様に書けるんですね。 このまま実行してもそのまま希望する状態を受け取ることができました。 問題はこの後同じようなこと(今回受け取った内容を利用しながらapiを叩かなければならない)を繰り返す際にどのように書けば良いのか...というところかと思っています。 今一度分解して見ていこうかと思います。 ありがとうございます。
guest

0

(追記)何故駄目なのかを追記しました。

forEachの中では問題なくconsole.logで出力出来ているのですが、forEachを出てしまうと、下記コード内jsonTeamが出力出来なくなってしまっています。

forEachでは解決出来ません。
Node.jsのコールバック周りの仕様を把握しましょう。

ざっくり解説すると、Node.jsやJavaScriptは元々シングルスレッドなのでHTTP通信等の他にボトルネックのある作業中はリソースの無駄ですよね。
それは嫌なので、通信系のメソッドにはコールバック関数を設定して、
「通信終わったら、引数として渡しておいたこの関数実行しといてね。それじゃ俺は下の処理を実行するから〜」
…という命令を出した後、通信の結果を待たずに下の行の処理を実行し始めます。

この仕様により、例えば5箇所のAPIにリクエストを投げて、
その結果を配列で受け取るというのは普通のfor文では不可能です。
5個のリクエストを同時に発射してそのまま下の行を実行するので、下の方でconsole.log(jsonTeam);とすると空の配列になってしまうのです。

なので、普通にやった場合、
1回目のリクエストのコールバックに2回目のリクエストを発射するという風に、
関数を5重にネストして、その後のコールバック関数内でconsole.log(jsonTeam);とすれば要素がちゃんと5つ入った配列を取得出来るのですが、
そんなことしたくないですよね…?

5リクエストが固定ならまだしも、4つや6つに増えたり減ったりする場合また書き直しかよ…
というわけで、何かしらの特殊なラッパーが必要になります。


JavaScriptは如何にしてAsync/Awaitを獲得したのか Qiita版
これは目を通してください。
頭から読めばしっくり来る答えにたどり着けると思います。

下記はasyncを利用した解決方法です。
思いつかなければこれでとりあえずいいんじゃないですかね?

JavaScript

1var http = require('http'); 2var https = require('https'); 3var Slack = require('slack-node'); 4var async = require("async"); 5 6var apiTokens = [], getTokens; 7// apiTokens.push(xxx);を繰り返す 8 9console.log('start'); 10 11// 全部コールバックの関数にしてしまう 12getTokens = apiTokens.map(function(apiToken){ 13 return function(next){ 14 var slack = new Slack(apiToken); 15 slack.api("team.info", function(err, response) { 16 delete(response.team.email_domain); 17 var icon = response.team.icon.image_original; 18 delete(response.team.icon); 19 response['team']['icon'] = icon; 20 next err, response.team; 21 }) 22 } 23}) 24 25// 第二引数の関数の第二引数に、上のnextの第二引数の配列が入ってくる。 26async.series(getTokens, function(err, Teams){ 27 console.log(JSON.stringify(Teams, null, 2)); 28})

追記

Array.mapはMDNにあるリスト操作系のメソッドです。
配列の全ての要素に同じ処理を適用したものを返します。

forEachとは同じリスト操作系メソッドの兄弟みたいなものですが、forEachはサブルーチンなので処理した結果は捨てられます。
mapは処理した結果を戻り値として持ち帰って来ますので、特性からふさわしい方を使いわける事が一般的です。

JavaScript

1var add_1 = function(it){ return it + 1; }; 2[1, 2, 3, 4, 5].map(add_1); 3// [2, 3, 4, 5, 6] 4 5var fns = [1, 2, 3, 4, 5].map(function(it){ 6 return function(){ 7 console.log(it); 8 }; 9}); 10// [fn, fn, fn, fn, fn] 11 12fns[0](); 13// 1

更に追記:mapとasyncを併用して可変の要素を順番に実行
async.seriesのページを見ると捗りそうですのでリンクを貼ります。

JavaScript

1var functions = [ 2 function (callback) { callback null, 1; }, 3 function (callback) { callback null, 2; } 4]; 5async.series(functions, function(err, results){ 6 console.log(results); 7 // [1, 2] 8});

これの仕組みを応用して作ったのが上記コードです(一部Typoがあったので修正済)
apiToken変数を束縛しながら、async.seriesが望むコールバック関数を持つ配列に加工しています。

JavaScript

1getTokens = apiTokens.map(function(apiToken){ 2 return function (callback) { 3 // apiTokenを利用してSlackと通信 4 callback null, 「通信結果」; 5 } 6}) 7 8console.log(getTokens); 9// [ 10// function (callback) { callback null, 「通信結果1」; }, 11// function (callback) { callback null, 「通信結果2」; }, 12// function (callback) { callback null, 「通信結果3」; }, 13// function (callback) { callback null, 「通信結果4」; }, 14// function (callback) { callback null, 「通信結果5」; } 15// ] 16 17async.series(getTokens, function(err, results){ 18 console.log(results); 19 // [「通信結果1」, 「通信結果2」, 「通信結果3」, 「通信結果4」, 「通信結果5」] 20});

※このやり方はあくまで発展途上のものの一つです。
なので動作確認後、必ず他の回答者さんの答えや
JavaScriptは如何にしてAsync/Awaitを獲得したのか Qiita版の記事を読んで上手いやり方を模索してみてください。

投稿2017/02/13 06:11

編集2017/02/13 08:01
miyabi-sun

総合スコア21158

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

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

sasagar

2017/02/13 07:17 編集

ありがとうございます。 自分のやりたいこととは違う例を見てしまうとなかなかうまく理解できずに苦しんでいるところです。 頂いたリンクも、一度読んでみたのですが、今脳内が煮えくりかえっている所為かイマイチ把握できず...という感じになってしまったので、時間をおいて改めて目を通そうと思います。 頂いたコード、一部修正しつつ実行してみたのですが、最後のtokenを利用したもののみが取得されてしまいました。 一旦、「//略」として頂いている部分を何もしない状態にし、そのままresultsに渡してしまいましたが、出力されるのは最後の一回分のみ...の様子です。 mapを使った事も無く、不慣れな所為もあるかもしれないのですが、何か追加で解説頂けたら幸いです。 --- 追加解説ありがとうございます。 シンプルな例だとイメージしやすいですね。 頂いたモノを踏まえ、調整し、動作確認できましたら、解決したコードを記載しようと思います。 ありがとうございます。
miyabi-sun

2017/02/13 07:27

質問文に沿った回答と、mapの解説を追記しました。 長くなってしまいましたが、もう一度上からご確認をお願いします。
sasagar

2017/02/13 07:36

ありがとうございます。 通信が終わるまで待たせなければいけないという点は把握しているのですが、どのようにすれば良いのか...というところの方法論がイメージ出来ずに困っておりました。 mapの部分を改めて把握し直したら解決出来そうな気もしてきています。
miyabi-sun

2017/02/13 08:02

申し訳ありません。 最初の回答部分のコードにTypoがありました。 追記&修正しています。
sasagar

2017/02/13 08:04

更に追記頂いてありがとうございます。 node.jsのメリットを活かすには、非同期処理になれるべきとは思いつつも、多言語で慣れているforEachに目が向いてしまうところが辛いです。 追加頂いた部分も改めて分解しながら考えてみようと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問