Node.js Advent Calendar 4日目!
どうぞよろしくお願いいたします。m(_ _)m
はじめに
Node.js で async await
について書くんですが、独学で理解した気になっていた私は、いざ実装となったときに『あれれ?』ってなりました。
知識が経験として定着してなかったんですね。反省(=_=)
そこで、読んだ方が試せるようになるべく細かく書いてみました!
自身の復習と定着にもなりますしね~♪(*‘∀‘)
所用時間は事前準備で 15 分、実作業で 15 分の、合計 30分程度です。
Node.js の非同期処理にまだ慣れていない方は、是非ともやってみてください♪
やりたいこと
やりたいことは非同期処理の勉強!
…なんですけど、そうではなくてどんなプログラムを作るかですね。(;^ω^)
今回は非同期処理の練習なので API Request をして情報を取得し、情報リストを作成していきたいと思います。
誰でも試せるように無料でできる範囲にしたいので、LINE WORKS API のトーク Bot API 縛りでお送りしたいと思います(*´Д`)
トーク Bot API 縛りだと・・・今まで作成した Bot リストが作れそうですね。
Bot リスト自体は練習なのであんまり使い道がないですが、有料プランとかだとユーザデータやカレンダー情報などを API Request で取得してリスト化する!とかもできます。
リスト作りは汎用性が高いので練習には持ってこないんですよ~。(゚Д゚)
ロードマップ
こんな感じで進めて行こうと思います。
- API を利用するための Access Token を発行する
- 発行した Access Token を使用して BOT リストの照会 API を Request する(非同期処理)
- さらに、Loop 処理で各 BOT の詳細情報照会 API をいっぺんに Request する
- Response を受け取ってくっつけて、リストを作る
この通りやりたいんだけど・・・!
ご存じの通り API Request はその性質上 Response を受け取るまで多少の時間がかかります。
しかし、Node.js は処理を同期させずに行うノンブロッキング I/O を利用しているので Response を待たずに次の処理を行います。
必要な情報が手に入っていないのに、次の手順へと進んでしまうのですね。
これはまずい(; ・`д・´)
そこで非同期処理と再起処理の記述が必要になってきます。
これらを順番に学んでいきたいと思います。
開発環境
Windows 10
Node.js
Visual Studio Code
LINE WORKS
使用する API
API は LINE WORKS API を利用します。
無料で使える Free Plan でも Bot 関係の API は使用できるので、勉強や検証するには便利なのです。
宣伝です(`・ω・´)キリッ
事前準備
それぞれ解説しているサイトの URL を貼っておきます。
取り敢えずやり方だけ見たいって方は先に進んじゃってくださいませ。
各モジュールのインストール
事前準備がすべて終わったら Visual Studio Code(以後 VSC)を起動します。
[ファイル] - [フォルダを開く] で作業用フォルダを開きます。
作業用フォルダは何でも良いので、デスクトップに新しいフォルダを作成します。
ファイル名も何でも良いのですが、ここでは ApiRequestSample
とでもしておきます。
[ターミナル] - [新しいターミナル] でターミナルを起動して npm init -y
します。
package.json
ファイルがフォルダに作成されたら、使用するモジュールをインストールしていきます。
今回使うのはこの4つ。
- request
- request-promise
- promise
- jsonwebtoken
ターミナルで順番にインストールしていきます。
> npm install request --save
> npm install request-promise --save
> npm install promise --save
> npm install jsonwebtoken --save
これで準備完了です。
ではでは、実際にコードを書いていきましょう!
1. API を利用するために Access Token を発行する
LINE WORKS API を利用するためは Access Token が必要です。
LINE WORKS 認証サーバーへの Token リクエスト API を使用して発行します。
事前準備の 4. で取得した LINE WORKS 認証情報を使いますので、ご用意を!
API Request で Token を取得するのですが、Response を待って次の処理を行う必要があるので、ここで非同期処理をする必要があります。
では、Node.js の非同期処理である async function
を使っていきましょーヾ(´∀`)ノ
async function のお約束
非同期処理をしてくれる、とっても便利な関数。
今回、私が理解したお約束はこんな感じ。
-
async function
ではPromise
オブジェクトを値を入れてreturn
する。 -
Promise
オブジェクトに値を入れるにはresolve
を使う。 -
async function
がエラー値をthrow
した場合はreject
を使う。
await を使って非同期処理!
await
さんは async function
が結果を返すまで処理を待ってくれる心の広いお方!
await
さんとのお約束はこんな感じ。
-
await
を頭につけてasync function
を使う。 -
await
を使えるのはasync function
の中でだけ。 - なので main プログラム内で
await
したいときは予めasync function main()
を宣言しておく。
まぁ、文字で読んでもイメージ沸きませんよね。( ゚Д゚)
なので、上記のことを踏まえてコードを見ていきましょう。
Access Token を発行するコード
使用するときは最初の CONSTS
のところに事前準備の 4. で取得した LINE WORKS 認証情報を記入してくださいねー。
// LINE WORKS 認証情報
const CONSTS = {
API_ID : "xxxxxxxx",
CONSUMER_KEY : "xxxxxxxxxx",
SERVER_ID : "xxxxxxxxxx",
PRIVATEKEY : "xxxxxxxxxxxxxxxxxxxxxxyyzz" // 認証キー
}
// module
const request = require("request-promise");
// program start
main();
/**
* 非同期処理にて Access Token を取得して表示
*/
async function main() {
const token = await getServerToken();
console.log(token);
}
/**
* LINE WORKS の Server Token を取得
* @return {object} Server Token
*/
async function getServerToken() {
const jwt = require("jsonwebtoken");
const iss = CONSTS.SERVER_ID;
const iat = Math.floor(Date.now() / 1000);
const exp = iat + 60 * 60; // 有効期間を 1時間に設定
const cert = CONSTS.PRIVATEKEY;
const options = {
method: "POST",
url: `https://auth.worksmobile.com/b/${CONSTS.API_ID}/server/token`,
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
form: {
grant_type: encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer"),
assertion: jwt.sign({ iss: iss, iat: iat, exp: exp }, cert, { algorithm: "RS256" }),
}
};
return new Promise((resolve, reject) => {
request(options).then((res) => {
const result = JSON.parse(res);
if (result.message) throw "Could not get token";
resolve(result);
}).catch((error) => {
console.log(`Auth Error: ${error}`);
reject(error);
});
});
}
> node .\main.js
{ access_token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
token_type: 'Bearer',
expires_in: 86400 }
無事に取得して表示することができました!ヾ(´∀`)ノ
次は、取得した Access Token を使用して API を Request していきましょう。
2. 発行した Access Token を使用して BOT リストの照会 API を Request する
Access Token が手に入ったので API Request し放題ですね!( ゚Д゚)
今回の目的は、トーク BOT のリストを作ることなので、まずは トーク Bot リストの照会 API で BOT の簡易リストを取得します。
ちなみに、BOT 登録がないと当たり前のように空のリストが返ってくるので、実際に試してみたい方は以下の記事を参考に適当な BOT を複数登録しておいてください。
【LINE WORKS】トーク BOT の登録方法
API Request するときは非同期処理をしよう!
繰り返しになりますが、API Request はその性質上 Response を受け取るまで多少の時間がかかります。
しかし、Node.js はノンブロッキング I/O を利用しているので Response を待たずに次の処理を行います。
なので、これも非同期処理をしないとろくな結果になりません(・ω・)
ではでは、軽くおさらいしながらコードを書いていきましょー。
BOT の簡易リストを取得するコード
BOT の簡易リストを取得する関数、getBotList()
を非同期処理の async function
で宣言します。
main()
の中で呼び出すときは await
を頭につけて呼び出します。
// 前略
main();
async function main() {
const token = await getServerToken();
const bots = await getBotList(token);
console.log(bots);
}
// getServerToken() の内容は省略
/**
* LINE WORKS のトーク Bot リストを取得
* @param {object} token LINE WORKS Server Token
* @return {Promise object} トーク Bot リスト
*/
async function getBotList(token) {
const options = {
method: "GET",
url: `https://apis.worksmobile.com/r/${CONSTS.API_ID}/message/v1/bot`,
headers: {
"Content-Type": "application/JSON",
"consumerKey": CONSTS.CONSUMER_KEY,
"Authorization": `${token.token_type} ${token.access_token}`
}
};
return new Promise((resolve, reject) => {
request(options).then((res) => {
const result = JSON.parse(res);
resolve(result.bots);
}).catch((error) => {
console.log(`Bot List Request Error:\n${error}`);
reject(error);
});
});
}
> node .\main.js
[ { botNo: xx6040,
name: 'お問い合わせフォーム君',
photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
i18nNames: [],
i18nPhotoUrls: [] },
{ botNo: xx6250,
name: 'facebook_bot',
photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
i18nNames: [],
i18nPhotoUrls: [] },
{ botNo: xx6261,
name: 'LW_bot',
photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
i18nNames: [],
i18nPhotoUrls: [] },
{ botNo: xx6682,
name: 'GAS 連携 Bot',
photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
i18nNames: [],
i18nPhotoUrls: [] }]
無事に Access Token
を取得してから API を呼び出すことができました。
また、botList
を取得してからコンソールに表示することもできました!
非同期処理、二回もやったので慣れてきましたね!ヾ(´∀`)ノ
async
!await
!(゚Д゚)ノ
async
!await
!(゚Д゚)ノ
呪文のように覚える。
もう非同期処理は完璧ですかね?
まだ不安ー!って方は、最後にもう1回やってみましょー!ヾ(´∀`)ノ
3. さらに、Loop 処理で各 BOT の詳細情報照会 API をいっぺんに Request する
先ほど取得した BOT 簡易リストの情報で、各 BOT の botNo
がわかりました。
botNo
がわかればトーク Bot 詳細情報の照会 API を利用して、各 BOT の詳細情報を照会できます。
つまり、リストアップされた BOT の数だけ API Request をするんですね。
API Request を Loop 処理する
繰り返しになりますが、API Request はその性質上 Response を受け取るまで多少の時間がかかります。
しかし、Node.js はノンブロッキング I/O を利用しているので Response を待たずに次の処理を行います。
Loop 処理なんかはその最たるもので、普通に for
や forEach
で回そうものなら処理順がぐっちゃぐっちゃになります。( ;∀;)
と、言うわけで!もはや耳タコな非同期処理を使って綺麗なループ処理をやっていきましょー!(*‘∀‘)
非同期処理で Loop するコード
// 前略
main();
async function main() {
const token = await getServerToken();
const bots = await getBotList(token);
let botLists = [];
for(let i = 0; i < bots.length; i++){
botLists.push(await getBotInfo(bots[i].botNo, token));
}
console.log(botLists);
}
// getServerToken() の内容は省略
// getBotList(token) の内容は省略
/**
* LINE WORKS トーク Bot の詳細情報を加えたリストを作成
* @param {object} bots LINE WORKS トーク Bot の botNo リスト
* @param {object} token LINE WORKS Server Token
* @return {Promise object} 詳細なトーク Bot リスト
*/
async function getBotInfo(botNo, token) {
const options = {
method: "GET",
url: `https://apis.worksmobile.com/r/${CONSTS.API_ID}/message/v1/bot/${botNo}`,
headers: {
"Content-Type": "application/JSON",
"consumerKey": CONSTS.CONSUMER_KEY,
"Authorization": `${token.token_type} ${token.access_token}`
}
};
return new Promise((resolve, reject) => {
request(options).then((res) => {
let result = JSON.parse(res);
result.botNo = botNo;
resolve(result);
}).catch((error) => {
console.log(`Bot Info Request Error:\n${error}`);
reject(error);
});
});
}
> node .\main.js
[ { name: 'お問い合わせフォーム君',
photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
useCallback: false,
tenantId: xxxxxxxx,
createdTime: 1544425101360,
modifiedTime: 1544425101360,
description: 'googleのフォームからのメッセージを受けてサンプル太郎に送信します。',
managers: [ 'xxxx@yyy-zzz' ],
submanagers: [],
domainInfos: [ [Object] ],
useGroupJoin: true,
useDomainScope: false,
i18nNames: [],
i18nPhotoUrls: [],
i18nDescriptions: [],
botNo: xxxxxxxx },
// 中略
{ name: '湯婆婆',
photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
useCallback: true,
callbackUrl: 'https://callback.com/',
callbackEvents: [ 'text' ],
tenantId: xxxxxxxx,
createdTime: 1605684025704,
modifiedTime: 1605684176017,
description: '流行りの湯婆婆をBOTで作ってみました。',
managers: [ 'xxxx@yyy-zzz' ],
submanagers: [],
domainInfos: [ [Object] ],
useGroupJoin: false,
useDomainScope: false,
i18nNames: [],
i18nPhotoUrls: [],
i18nDescriptions: [],
botNo: xxxxxxxx } ]
description
や callbackUrl
など、Bot の詳細情報を取得することができましたね!
三回も非同期処理の練習をしたので、これでもうバッチリ OK 大丈夫!
右手に async
! 左手に await
!
合体させて非同期処理!
はい、お粗末様でしたー( ゚Д゚)
コード全体
最後に、コード全体をさらしておきます。
// LINE WORKS 認証情報
const CONSTS = {
API_ID : "xxxxxxxx",
CONSUMER_KEY : "xxxxxxxxxx",
SERVER_ID : "xxxxxxxxxx",
PRIVATEKEY : "xxxxxxxxxxxxxxxxxxxxxxyyzz" // 認証キー
}
// module
const request = require("request-promise");
// main
main();
async function main() {
const token = await getServerToken();
const bots = await getBotLists(token);
let botLists = [];
for(let i = 0; i < bots.length; i++){
botLists.push(await getBotInfo(bots[i].botNo, token));
}
console.log(botLists);
}
/**
* LINE WORKS の Server Token を取得
* @return {Promise object} Server Token
*/
async function getServerToken() {
const jwt = require("jsonwebtoken");
const iss = CONSTS.SERVER_ID;
const iat = Math.floor(Date.now() / 1000);
const exp = iat + 60 * 60; // 有効期間を 1時間に設定
const cert = CONSTS.PRIVATEKEY;
const options = {
method: "POST",
url: `https://auth.worksmobile.com/b/${CONSTS.API_ID}/server/token`,
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
form: {
grant_type: encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer"),
assertion: jwt.sign({ iss: iss, iat: iat, exp: exp }, cert, { algorithm: "RS256" }),
}
};
return new Promise((resolve, reject) => {
request(options).then((res) => {
const result = JSON.parse(res);
if (result.message) throw "Could not get token";
resolve(result);
}).catch((error) => {
console.log(`Auth Error: ${error}`);
reject(error);
});
});
}
/**
* LINE WORKS のトーク Bot リストを取得
* @param {object} token LINE WORKS Server Token
* @return {Promise object} トーク Bot リスト
*/
async function getBotLists(token) {
const options = {
method: "GET",
url: `https://apis.worksmobile.com/r/${CONSTS.API_ID}/message/v1/bot`,
headers: {
"Content-Type": "application/JSON",
"consumerKey": CONSTS.CONSUMER_KEY,
"Authorization": `${token.token_type} ${token.access_token}`
}
};
return new Promise((resolve, reject) => {
request(options).then((res) => {
const result = JSON.parse(res);
resolve(result.bots);
}).catch((error) => {
console.log(`Bot List Request Error:\n${error}`);
reject(error);
});
});
}
/**
* LINE WORKS トーク Bot の詳細情報を加えたリストを作成
* @param {object} bots LINE WORKS トーク Bot の botNo リスト
* @param {object} token LINE WORKS Server Token
* @return {Promise object} 詳細なトーク Bot リスト
*/
async function getBotInfo(botNo, token) {
const options = {
method: "GET",
url: `https://apis.worksmobile.com/r/${CONSTS.API_ID}/message/v1/bot/${botNo}`,
headers: {
"Content-Type": "application/JSON",
"consumerKey": CONSTS.CONSUMER_KEY,
"Authorization": `${token.token_type} ${token.access_token}`
}
};
return new Promise((resolve, reject) => {
request(options).then((res) => {
let result = JSON.parse(res);
result.botNo = botNo;
resolve(result);
}).catch((error) => {
console.log(`Bot Info Request Error:\n${error}`);
reject(error);
});
});
}
おわりに
ここまでお付き合いいただきありがとうございました。
早いもので、独学で Node.js を勉強し始めてから 2年が経ちました。
非同期処理さんとも、何だかようやく仲良くなれた気がします♪
最近はソロ開発ばかりではなく先輩と組むこともあり、色々と吸収させてもらっています。
ほんと、良い先輩に恵まれた!
メキメキ成長して、恩返しをしたい所存であります。
次は何を書こうかな~。
ではまた!(^^)/