- 投稿日:2019-06-22T23:34:18+09:00
nodenvで最新のNode.jsを入れてみる
lightsail起動して、nodenvでNode.js入れるまで。普通にlithgsailのNode.js入ってるイメージ使えばいいんだけど、何か色々の用途で使いたかったからプレーンなOSスタートだった。
nodenvインストールする
https://github.com/nodenv/nodenv
この辺見ながら上からする。
% git clone https://github.com/nodenv/nodenv.git ~/.nodenv % cd ~/.nodenv && src/configure && make -C src使ってるshellはzshなので指示に従ってzshrcでPATHを通す。
~/.zshrcexport PATH="$HOME/.nodenv/bin:$PATH"initしろって書いてあるからする。
% ~/.nodenv/bin/nodenv init # Load nodenv automatically by appending # the following to ~/.zshrc: eval "$(nodenv init -)"なぜzshrcにもう一度書き込まなきゃならないのか分からないけど、書いてあるからする。
~/.zshrcexport PATH="$HOME/.nodenv/bin:$PATH" eval "$(nodenv init -)"
nodenv-doctor
とやらが状態を見てくれるというのでする。これで全部OKって出るらしい。% curl -fsSL https://github.com/nodenv/nodenv-installer/raw/master/bin/nodenv-doctor | bash Checking for `nodenv' in PATH: /home/ec2-user/.nodenv/bin/nodenv Checking for nodenv shims in PATH: OK Checking `nodenv install' support: not found Unless you plan to add Node versions manually, you should install node-build. Please refer to https://github.com/nodenv/node-build#installation Counting installed Node versions: none There aren't any Node versions installed under `/home/ec2-user/.nodenv/versions'. You can install Node versions like so: nodenv install 2.2.4 Auditing installed plugins: OK
nodenv install
がnot found
って言ってて急に裏切られた気になる。node-build入れる
Optionalって書いてあるけど、
node-build
を入れなきゃだめみたいだ。https://github.com/nodenv/node-build#readme
上記を見ると、インストール先が
/usr/local
になってる。今までec2-userでnodenv入れようとしてたけど、大丈夫なのかな? PATHが通ってればいいのかもしれない。rootになって下記をした。$ git clone https://github.com/nodenv/node-build.git /usr/local/src $ PREFIX=/usr/local /usr/local/src/node-build/install.sh実行しても何も表示されないけど、これでいいっぽい。無慈悲。ec2-userに戻って、doctorに聞いてみる。
% curl -fsSL https://github.com/nodenv/nodenv-installer/raw/master/bin/nodenv-doctor | bash Checking for `nodenv' in PATH: /home/ec2-user/.nodenv/bin/nodenv Checking for nodenv shims in PATH: OK Checking `nodenv install' support: /usr/local/bin/nodenv-install (node-build 4.6.1) Counting installed Node versions: none There aren't any Node versions installed under `/home/ec2-user/.nodenv/versions'. You can install Node versions like so: nodenv install 2.2.4 Auditing installed plugins: OKあ、installが使えるようになったっぽい。
最新のnode入れてみる
インストールできるバージョン確認
% nodenv install -l ・・・ 12.4.0 ・・・・インストール
% nodenv install 12.4.0 Downloading node-v12.4.0-linux-x64.tar.gz... -> https://nodejs.org/dist/v12.4.0/node-v12.4.0-linux-x64.tar.gz Installing node-v12.4.0-linux-x64... Installed node-v12.4.0-linux-x64 to /home/ec2-user/.nodenv/versions/12.4.0結構早く終わる。
% node --version nodenv: node: command not found The `node' command exists in these Node versions: 12.4.0見つからないのか、見つかったのか、どっちなのか……。で、これはきっとアレだと思った。
use
とかglobal
でバージョン指定してないからだと思った。global
らしい。% nodenv global 12.4.0 % node --version v12.4.0 % npm --version 6.9.0あ、できた。
- 投稿日:2019-06-22T21:30:52+09:00
Node for MaxでTypeScriptを使う
つくったもの
Node for Max向けに簡単なTypeScriptの開発環境を作りました。
Max APIの型定義が効いているので、快適に開発ができます。
ソースコード: github
機能
ts-nodeモード
Max 8.0.4のアップデートで
node.script
にCLIオプションが渡せるようになりました。これによって、 nodeの
-r
オプションが使えるようになったので、ts-node/registerを使うことができます。つまり、
.js
を吐かずにTypeScriptを実行できて、REPLとかもできちゃうし、 ファイルを吐き出す時間がないので、小さなプロジェクトなら実行にかかる時間もjsとほとんど変わらずサクサクです。tscモード
tscを使ってトランスパイルしたファイルを通常通りに吐き出せるようにもしてあるので、ts-nodeが嫌いな人はそちらで。
ボイラープレートの構成(省略版)
├── @types
│ └── max-api.d.ts <-- MaxAPIの型定義ファイル
├── index.ts <--ts-nodeモード
のエントリーポイント
├── lib/ <-- tscしたらここに入る
├── main.maxpat <-- Maxはこれを開く
├── node_modules
├── package.json
├── run-transpiled.js <--tscモード
の時のエントリーポイント
└── tsconfig.jsonボイラープレートについて
これに対応するコードが
index.tsimport * as MaxAPI from 'max-api' import * as fs from 'fs' import { say } from 'cowsay' import { Log } from './utils' enum actionTypes { LIST_FILES = 'list-files', SAY = 'say' } MaxAPI.addHandler(actionTypes.LIST_FILES, () => { fs.readdir(process.cwd(), (err, files) => { if (err) { MaxAPI.post(err.message, "error") } const text = say({ text: 'Node for Max is Awesome!' }); Log(...files) MaxAPI.outlet(...files, text) }) }) MaxAPI.addHandler(actionTypes.SAY, (...whatever: string[]) => { const text = say({ text: whatever.join(' ') }) Log(text) MaxAPI.outlet(text) })こんな感じ。(サンプルがcowsayかよというツッコミはさておき、、)
で、umenuから
ts-node
を使うかtsc
を使うか選択すると、こんな感じでスイッチングされる。
node.script
の@options
に渡しているのがそのままnodeに渡されるのでこれはつまりnode -r ts-node/register index.ts
ということ。 (
-r
オプションは指定したライブラリを実行前にrequireしてくれる)
tscモード
の場合は右のnode.script run-transpiled.js
(※.jsに注目)が実行される。これは単純に、
run-transpiled.js/** * @description This file should only be called after source code is transpiled. * @author Yuichi Yogo */ require('./lib/index')
lib/
にtscでトランスパイルしたjsファイルが入ることが期待されているので(そこの同期処理はMaxパッチ側で書いてある)、それをシンプルにrequireするだけ。まとめ
今回のボイラープレート作成にあたり、Max APIの型定義ファイルをCylclingがpublishしていないので、自分で用意しました。
とても不便なのでcycling '74にお願いしておきました。(対応してくれるかどうかは知らない)そのうち
npm install
で最新の型が手に入るようになるといいですね!
- 投稿日:2019-06-22T18:56:23+09:00
toioを動かす(Node.js)
toio動いたー pic.twitter.com/Vsdoo7a7RF
— kazukinakamura (@kazukinakamur14) June 22, 2019公式
https://github.com/toio/toio.js#computer-prerequisites動作環境
node -v v8.16.0動作確認
git clone https://github.com/toio/toio.js.git # clone repository cd toio.js # move to repository root yarn install # install dependencies yarn build # build @toio/* packages yarn example:keyboard-control # start sample application (see below)yarnがないときは適宜
durationに0を設定すると楽しい!
- 投稿日:2019-06-22T17:52:31+09:00
【2019年6月版】jsのクロージャとconst
結論
クロージャの中で使いたい変数の宣言に注意しよう。
- 参照のみの変数は
const
宣言マスト。でもあとは気にしなくてOK。- 書き込みしたい変数は
let
宣言して腹をくくる。時々、忘れてしまうのでメモです。
クロージャ内から使用される変数に刮目せよ
js ではクロージャをバンバン使いますが、クロージャ内で使用する変数の宣言を適当にしてしまうとバグを埋め込む原因となります。
例えば以下のようなコードで、// DB や APIなどから非同期で処理〜 function getPromise(c) { return new Promise((accept)=>accept(c)); } // 何かのマスターを元にループで処理したい const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }) msg = `[${a}] <- 終了!お疲れ様でした。` console.log(msg); }
getPromise(a).then(v => {...}
で変数a
の値が変数v
に渡されてきますが、このv => {...}
の中で参照されている変数msg
の宣言はどうなっているでしょうか?
msg = '[${a}] <- イマココ'
で宣言無しにいきなり変数定義してしまっています。熟練の方は一瞬でをいをいおと気づいていただけるでしょうが、例えば僕は数千行に渡るデータコンバート用の単発のバッチを涙ながらにjsでガリガリ書いているわけです。DB
A
の テーブルB
からデータを読み込み、 DBC
の テーブルD
のマスターを参照しつつデータを変換して DBE
の テーブルF
へデータを書き込んでいくわけです。いちいち変数宣言してられるかーーー!!・・・とりあえず区切りのいいところで試しに動かして見るか、とテスト環境を対象に上記のバッチを流してみると・・・
$ npm start [1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。
処理中:
のメッセージでイマココ
を確認したかったのに、終了
メッセージが表示されています。ウホー!
最初の5行の出力はちょっと目障りなので出力をやめてみましょう。function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); msg = `[${a}] <- 終了!お疲れ様でした。`; }これを実行してみます。
$ npm start > node index.js 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。終了メッセージに書き換わっている・・・。
いやいや、
getPromise(a)
の後でmsg = `[${a}] <- 終了!お疲れ様でした。`;
とmsgの代入処理をしているのでこの問題点はすぐに気づけるはずです。俺としたことが・・・ちょっと疲れたのかな。。。それではこれではどうでしょう?
function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); }
getPromise(a)
の後でmsg
には何も入れていません。というかmsg
は宣言してからクロージャの中でしか使用していません。よし、万全。指差し確認、OK!では、バッチを動かしてみましょう。
$ npm start > node index.js 値: 1 処理中: [5] <- イマココ 値: 2 処理中: [5] <- イマココ 値: 3 処理中: [5] <- イマココ 値: 4 処理中: [5] <- イマココ 値: 5 処理中: [5] <- イマココ
イマココ
メッセージが全部5
になっているーーー!!!
これはメッセージに出力しているだけなので、msg
が変わってしまっていても別にフーンで済むと思うかもしれません。
では、このmsg
変数がDBの書き込み先だったり、APIのエンドポイントだったと想定したらどうでしょう?
すべてのデータが5番の接続先へ流れていきます。APIやRDBMSなら引数エラーや最悪でもスキーマの不一致などでフェイタルエラーで止まってくれるかもしれませんが、MongoDBやKVSなどのスキーマレスのNoSQLやただのフォルダ、アップロードするだけなどの場合は普通に処理が完了してしまうでしょう。
もう、白目、ですね・・・。想像しただけで胃が縮む思いです。どうしてこうなったのか・・・? 理解するのには最初の冗長なメッセージを出していたバージョンのプログラムをもう一度、見るのがわかりやすいのです。
function getPromise(c) { return new Promise((accept)=>accept(c)); } for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); msg = `[${a}] <- 終了!お疲れ様でした。`; console.log(msg); }そして実行結果は、
$ npm start [1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。となります。
forループの最後で宣言した
console.log(msg);
の結果である、[1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。がすべて先に表示されています。
つまり、先にfor ループ が完了している ということです。
Promise
で処理を後回しにしたので、すべての for ループが完了してから、Promise
の(accept)=>accept(c)
が呼び出され、v => {...}
が呼び出されます。・・・
for
ループが完了しているのに、for ループ内で宣言した変数msg
はどうなっているのか・・・?これがクロージャがクロージャたる所以なのですが、
v => {...}
が宣言された時点でのすべての変数がレキシカル環境として記憶されています。要はどこかで変数msg
の存在を覚えててくれるわけですが、この変数が書き込み有りか無しかで持ち方が変わってしまう、というのがキモなワケです。今回は手抜きをして変数宣言をすっ飛ばした結果、デフォルトの宣言である書き込み可能な
var
として定義されてしまいました。
よってクロージャの中では書き込み用のmsg
というみんなで読み書きできる1つの場所
として変数がmsg
が使用されています。その結果、forの最後のループで書き込まれた5
がすべてのクロージャで参照された、ということになります。これはクロージャの中から値を取り出したい場合のような
明確な書き込み用途
の変数の場合は当然の結果です。
というわけで、書き込み用途ではない場合には明確に読み取り専用変数であるconst
の宣言がマストなのです。ここでは
msg
は変わって欲しくない変数のため、以下のようにconst
宣言をちゃんとします。function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { const msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); }そして、実行してみると・・・
$ npm start > node index.js 値: 1 処理中: [1] <- イマココ 値: 2 処理中: [2] <- イマココ 値: 3 処理中: [3] <- イマココ 値: 4 処理中: [4] <- イマココ 値: 5 処理中: [5] <- イマココ
const
宣言をして、宣言した時点の値をちゃんとキャプチャさせた結果、ちゃんと 1〜5 のイマココ
メッセージが表示されました。やれやれだぜ。。。さらに今回は変数の宣言をせずにデフォルトの
var
が使われてしまいましたが、var
は関数スコープでfunction
オブジェクトのプロパティ
のように振る舞うので、副作用が大きいです。function majisuka() { var x = 'マジで?' let y = '嘘でしょ?' { var x = 'マジすか?' let y = '本当に?' } console.log(`x is [${x}]`); console.log(`y is [${y}]`); } majisuka();こんな適当なプログラムでも、ちゃんと結果を予測できますか?結果は以下のようになります。
x is [マジすか?] y is [嘘でしょ?]
var
で宣言したx
はブロック内で宣言したx
で見事に上書きされています。マジすか?!
そんなわけでクロージャ内で書き込みたい変数にはちゃんとlet
宣言をしましょう!!ちなみに java では
String msg = "こんにちわ"; // final 宣言していない new Thread(() -> { msg = "こんばんわ"; // -> これは"コンパイルエラー" System.out.println(msg); });などの、クロージャ外で宣言された変数へのアクセスはコンパイルエラーとなります。
final
宣言がMust
で読み取り専用の変数しか渡せません。例えば上記のように本当にマルチスレッドで動かすためのクロージャでは、クロージャの外部へ値を書き出すことはそのまま
スレッド間通信
となるので、変数への書き込みのようなお手軽な読み書きじゃダメなのですね。
このように場合に応じてロックのストラテジを各自でちゃんと考えて用意してね、というわけなのです。js はシングルスレッドで動くかなり特殊な言語で、クロージャが同時に実行されることはないので、一つの変数の読み書き、というレベルで済んでしまいます。お手軽なのはいいですが、副作用は大きいので気をつけてね!というお話でした〜。
参考ページ
- 投稿日:2019-06-22T17:52:31+09:00
【2019年6月版】jsでクロージャに渡す変数はとりあえずconstしておけッ!!
結論
クロージャの中で使いたい変数の宣言に注意しよう。
- 参照のみの変数は
const
宣言マスト。でもあとは気にしなくてOK。- 書き込みしたい変数は
let
宣言して腹をくくる。var
は使わない。時々、忘れてしまうのでメモです。
クロージャ内から使用される変数に刮目せよ
js ではクロージャをバンバン使いますが、クロージャ内で使用する変数には適切な宣言をしておかないとバグを埋め込む原因となります。
例えば以下のようなコードで、// DB や APIなどから非同期で処理〜 function getPromise(c) { return new Promise((accept)=>accept(c)); } // 何かのマスターを元にループで処理したい const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }) msg = `[${a}] <- 終了!お疲れ様でした。` console.log(msg); }
getPromise(a).then(v => {...}
で変数a
の値が変数v
に渡されてきますが、このv => {...}
の中で参照されている変数msg
の宣言はどうなっているでしょうか?
msg = '[${a}] <- イマココ'
で宣言無しにいきなり変数定義してしまっています。熟練の方は一瞬でをいをいおと気づいていただけるでしょうが、例えば僕は数千行に渡るデータコンバート用の単発のバッチを涙ながらにjsでガリガリ書いているわけです。DB
A
の テーブルB
からデータを読み込み、 DBC
の テーブルD
のマスターを参照しつつデータを変換して DBE
の テーブルF
へデータを書き込んでいくわけです。いちいち変数宣言してられるかーーー!!・・・とりあえず区切りのいいところで試しに動かして見るか、とテスト環境を対象に上記のバッチを流してみると・・・
$ npm start [1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。
処理中:
のメッセージでイマココ
を確認したかったのに、終了
メッセージが表示されています。ウホー!
最初の5行の出力はちょっと目障りなので出力をやめてみましょう。function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); msg = `[${a}] <- 終了!お疲れ様でした。`; }これを実行してみます。
$ npm start > node index.js 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。終了メッセージに書き換わっている・・・。
いやいや、
getPromise(a)
の後でmsg = `[${a}] <- 終了!お疲れ様でした。`;
とmsgの代入処理をしているのでこの問題点はすぐに気づけるはずです。俺としたことが・・・ちょっと疲れたのかな。。。それではこれではどうでしょう?
function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); }
getPromise(a)
の後でmsg
には何も入れていません。というかmsg
は宣言してからクロージャの中でしか使用していません。よし、万全。指差し確認、OK!では、バッチを動かしてみましょう。
$ npm start > node index.js 値: 1 処理中: [5] <- イマココ 値: 2 処理中: [5] <- イマココ 値: 3 処理中: [5] <- イマココ 値: 4 処理中: [5] <- イマココ 値: 5 処理中: [5] <- イマココ
イマココ
メッセージが全部5
になっているーーー!!!
これはメッセージに出力しているだけなので、msg
が変わってしまっていても別にフーンで済むと思うかもしれません。
では、このmsg
変数がDBの書き込み先だったり、APIのエンドポイントだったと想定したらどうでしょう?
すべてのデータが5番の接続先へ流れていきます。APIやRDBMSなら引数エラーや最悪でもスキーマの不一致などでフェイタルエラーで止まってくれるかもしれませんが、MongoDBやKVSなどのスキーマレスのNoSQLやただのフォルダ、アップロードするだけなどの場合は普通に処理が完了してしまうでしょう。
もう、白目、ですね・・・。想像しただけで胃が縮む思いです。どうしてこうなったのか・・・? 理解するのには最初の冗長なメッセージを出していたバージョンのプログラムをもう一度、見るのがわかりやすいのです。
function getPromise(c) { return new Promise((accept)=>accept(c)); } for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); msg = `[${a}] <- 終了!お疲れ様でした。`; console.log(msg); }そして実行結果は、
$ npm start [1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。となります。
forループの最後で宣言した
console.log(msg);
の結果である、[1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。がすべて先に表示されています。
つまり、先にfor ループ が完了している ということです。
Promise
で処理を後回しにしたので、すべての for ループが完了してから、Promise
の(accept)=>accept(c)
が呼び出され、v => {...}
が呼び出されます。・・・
for
ループが完了しているのに、for ループ内で宣言した変数msg
はどうなっているのか・・・?これがクロージャがクロージャたる所以なのですが、
v => {...}
が宣言された時点でのすべての変数がレキシカル環境として記憶されています。要はどこかで変数msg
の存在を覚えててくれるわけですが、この変数が書き込み有りか無しかで持ち方が変わってしまう、というのがキモなワケです。今回は手抜きをして変数宣言をすっ飛ばした結果、デフォルトの宣言である書き込み可能な
var
として定義されてしまいました。
よってクロージャの中では書き込み用のmsg
というみんなで読み書きできる1つの場所
として変数がmsg
が使用されています。その結果、forの最後のループで書き込まれた5
がすべてのクロージャで参照された、ということになります。これはクロージャの中から値を取り出したい場合のような
明確な書き込み用途
の変数の場合は当然の結果です。
というわけで、書き込み用途ではない場合には明確に読み取り専用変数であるconst
の宣言がマストなのです。ここでは
msg
は変わって欲しくない変数のため、以下のようにconst
宣言をちゃんとします。function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { const msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); }そして、実行してみると・・・
$ npm start > node index.js 値: 1 処理中: [1] <- イマココ 値: 2 処理中: [2] <- イマココ 値: 3 処理中: [3] <- イマココ 値: 4 処理中: [4] <- イマココ 値: 5 処理中: [5] <- イマココ
const
宣言をして、宣言した時点の値をちゃんとキャプチャさせた結果、ちゃんと 1〜5 のイマココ
メッセージが表示されました。やれやれだぜ。。。
var
はやばい。さらに今回は変数の宣言をせずにデフォルトの
var
が使われてしまいましたが、var
は関数スコープでfunction
オブジェクトのプロパティ
のように振る舞うので、副作用が大きいです。function majisuka() { var x = 'マジで?' let y = '嘘でしょ?' { var x = 'マジすか?' let y = '本当に?' } console.log(`x is [${x}]`); console.log(`y is [${y}]`); } majisuka();こんな適当なプログラムでも、ちゃんと結果を予測できますか?結果は以下のようになります。
x is [マジすか?] y is [嘘でしょ?]
var
で宣言したx
はブロック内で宣言したx
で見事に上書きされています。マジすか?!
そんなわけでクロージャ内で書き込みたい変数にはちゃんとlet
宣言をしましょう!!ちなみに java では
String msg = "こんにちわ"; // final 宣言していない new Thread(() -> { msg = "こんばんわ"; // -> これは"コンパイルエラー" System.out.println(msg); });などの、クロージャ外で宣言された変数へのアクセスはコンパイルエラーとなります。
final
宣言がMust
で読み取り専用の変数しか渡せません。例えば上記のように本当にマルチスレッドで動かすためのクロージャでは、クロージャの外部へ値を書き出すことはそのまま
スレッド間通信
となるので、変数への書き込みのようなお手軽な読み書きじゃダメなのですね。
このように場合に応じてロックのストラテジを各自でちゃんと考えて用意してね、というわけなのです。js はシングルスレッドで動くかなり特殊な言語で、クロージャが同時に実行されることはないので、一つの変数の読み書き、というレベルで済んでしまいます。お手軽なのはいいですが、副作用は大きいので気をつけてね!というお話でした〜。
参考ページ
- 投稿日:2019-06-22T13:20:37+09:00
CentOSへNode.jsをインストール
書いてあること
- CentOSへのNode.jsのインストール手順
環境
- CentOS Linux release 7.6.1810 (Core)
- Node.js v10.16.0
- Npm 6.9.0
リポジトリを作成
バージョン番号を確認
リポジトリを作成
bash$ curl -sL https://rpm.nodesource.com/setup_10.x | bash -リポジトリが作成されたことを確認
bash$ ls -l /etc/yum.repos.dインストール
Node.jsをインストール
bash$ yum install -y nodejsバージョンを確認
bash$ node --version $ npm --version
- 投稿日:2019-06-22T02:09:49+09:00
[LINE BOT]コスパの良いプロテインをおススメするLINE BOTを作りたかった
目次:
- 初めに
- 何でこんなモノ作ったの?
- 「プロテインチョイサー」の仕様
- 実際に作ってみた
- 開発環境
- 前準備
- 実際のコード
- QRコード
- 感想
- おまけ
- 作り方
- QRコード
初めに:
この記事はプロトタイピング専門スクール「ProtoOut Studio」の課題として作成しています。
最近、仕事がうまくいかない…
人間関係が不安…
現代社会を生きる上で、そういった悩みで煩悶することがままあるでしょう。どうすれば、それらの悩みを克服できるだろう…
それは、筋トレです。筋トレをすれば、あらゆる悩みは克服できます。
その筋トレをサポートするLINE BOTを作りました。
名前は「プロテインチョイサー」です。
なお、当記事は筋肉初心者向けです。安心して筋トレに活かしてください!何でこんなモノ作ったの?:
私は半年前から筋トレに励んでいまして、目標は「体脂肪率15%を切る」ことです。
ご存じの通り、筋トレには良質なたんぱく質が不可欠です。
なので、プロテインを常飲することが筋トレ界では支配的です。
しかし、プロテインには複数の種類が存在し、値段、成分、量もバラバラで選びにくい。プロテインは種類ごとに飲み方があり、間違った飲み方をすると逆に太ってしまうなどの悪影響が出てしまいます。これらの要素が厳しい壁となり、立ちはだかっています。特に、値段は誰しもが気になって仕方がないポイントでしょう。
もちろん、筋肉があれば乗り越えられます。
しかし、筋肉初心者には、やはり厳しいと思います。
これでは、思い通りの筋トレができない!
そういう方のために、当BOTを作りました。「プロテインチョイサー」の仕様:
・友達登録の際、あいさつとしてプロテイン解説サイトのURLを表示してきます。
筋肉初心者の方は、そこでプロテインについて勉強してください。・次に、知りたいプロテインの種類の名前を送ります。
すると、おススメのプロテインを表示してくれます。
タップするとプロテイン販売サイト(大体AMAZONか楽天)に移動するので、そこでプロテインを購入し、筋トレに励んでください。実際に作ってみた:
●開発環境
Node.js v10.16.0
npm v6.9.0
Windows10 pro●前準備
@n0bisukeさんの記事を参考にしてプロットを作っておく。●実際のコード
protein_choice.js
当コードの内容としては、以下の通りです。
「はい」のメッセージを受け取るとプロテインの種類名を聞いてきます。
次に「ホエイプロテイン」などの名称を入力すると、Googleショッピングタブにスクレイピングしに行き、条件に合ったプロテインURLを5個表示します。
※なお、価格帯は2500-5000、レビュースコア降順をデフォルトの設定としています。node.js'use strict'; // 使用パッケージ群 const express = require('express'); const line = require('@line/bot-sdk'); const clientFetch = require('cheerio-httpcli'); // LINE用定数群 const PORT = process.env.PORT || 3000; const config = { channelSecret: 'd309c32c2d348179e7e82506bf8272a2', channelAccessToken: 'KA5eK58WOGwt8RvsmHLHFkhStSRwy8JBtuE9w9FBZXYPbHJVviNTKYWekQOPsi9yqLO/vGHvC4HDqPxeDOg/zmVRrDrLeRyUYtaGa/W/RL4sxcrJ0eHCekRF9KcjLvvkhR6ZAi/Cwv8D+A8bRxsyogdB04t89/1O/w1cDnyilFU=' }; // スクレイピング用定数群 日本語だと文字コードの影響で文字化けするので、翻訳する const wordWhei = 'Wheyprotein'; const wordCasein = 'Caseinprotein'; const wordSoy = 'Soyprotein'; const url = 'https://www.google.com/search?' const googleUrl = 'https://www.google.com'; // マッスル用定数群 const proteinGoodbye = 'では、よい筋肉ライフを!'; const proteinRecommended = 'あなたにおススメのプロテインはコレだ!'; const proteinImportant = '言うまでもないが、最重要なのは規則正しい生活とバランスの良い食事です。'; const app = express(); app.post('/webhook', line.middleware(config), (req, res) => { console.log(req.body.events); Promise .all(req.body.events.map(handleEvent)) .then((result) => res.json(result)); }); const client = new line.Client(config); function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } let message = proteinRecommended; switch (event.message.text) {// メッセージの内容で処理分岐 case 'ホエイプロテイン': getProtein(event.source.userId, wordWhei) return client.replyMessage(event.replyToken, { type: 'text', text: message }); case 'カゼインプロテイン': getProtein(event.source.userId, wordCasein) return client.replyMessage(event.replyToken, { type: 'text', text: message }); case 'ソイプロテイン': getProtein(event.source.userId, wordSoy) return client.replyMessage(event.replyToken, { type: 'text', text: message }); default: // プロテイン以外のメッセージ対応 return client.replyMessage(event.replyToken, { type: 'text', text: proteinGoodbye + proteinImportant }); } } const getProtein = async (userId, text) => { // スクレイピング処理 let urlArray = []; let messageReply = ''; clientFetch.fetch(url, { // 検索条件指定(googleショッピングタブで価格指定、レビュースコア降順で検索) tbm: 'shop', q: text, vw: 1, mr: 1, price: 1, ppr_min: '2500', ppr_max: '5000', p_ord: 'rv' }, function (error, $, res) { $('a', '.eIuuYe').each(function (idx) { // プロテイン販売サイトのURLを5個まで取得 for (let i = 0; i < 5; i++) { urlArray[i] = googleUrl + $(this).attr('href') } messageReply = urlArray }); }); await client.pushMessage(userId, { type: 'text', text: messageReply, }); } (process.env.NOW_REGION) ? module.exports = app: app.listen(PORT); console.log(`Server running at ${PORT}`);●QRコード
未完成のBOTですが、『顰像』(しかみぞう)として公開します。
感想:
今まで余りnode.jsに触ってこなかったため、LINE message APIの仕様が今一把握できず、大分苦戦しました。
結局、課題を提出できなかったので、まだまだ筋肉が足りないと痛感します。
Webhookに繋げられなかったり、スクレイピングがうまくいかなかったり、そもそもAPIかましてないじゃん…と紆余曲折を経ました。
それにしても、Proto out studioの皆さんは発想も技術も凄い…
同じ生徒としてそこはかとない敗北感を感じますが、その感情を糧にして筋トレに励みます!
今回はGoogleのショッピングタブを使いましたが、行く行くはプロテインの成分、量、袋のタイプ等の検索条件を増やし、その人に最適なプロテインを表示できるようにしたいです。
価格.comで出来るじゃんとか言うな
そして、オリジナリティ溢れるUI、UXが高いBOTないしサービスを作り、ローンチしたい。綺麗なコーディングもしたい。そのためには、日々勉強、筋トレあるのみ。
筋肉は一日にして為らず。おまけ:
簡易版プロテインチョイサーなるモノも作ってみました。
これは、LINEオフィシャルアカウントマネージャーでリッチメニューを作成したものになります。
そして、私が作りたかったモノのプロトタイプです。●作り方
LINEオフィシャルアカウントマネージャーに移動する。
あいさつタブ(緑色の箇所)をクリック。メッセージを書いた後、”変更を保存”をクリック
LINEデベロッパーに移動し、QRコードをスキャンして友達になってみましょう。
プログラミングしなくてもここまでのモノが出来るとは、いい時代になったものだ…
- 投稿日:2019-06-22T00:01:18+09:00
Node.jsでAWSの請求金額をLINEで通知する
LINEDevelopersの設定をする
- LINEDevelopersに登録とチャンネル(通知が送られてくるアカウント)を作成する。
LINE Developers公式チャンネルを作ると、上のような設定状況を見ることができる。名前は「AWS料金通知」とした。
ここでAPIを使うためのアクセストークン
とメッセージを送信する先のユーザコード
を確認しておく。LINEAPIのプッシュメッセージでメッセージを送る
まず自分のLINEアカウントにメッセージを送信する。
const https = require('https'); const LINEMessage = (() => { let _postData = ''; let _message = ''; let _requestBody = new Object(); const setMessage = (data) => { _message = String(`今月のAWS概算請求金額は「${data}$」です。`); _requestBody.to = process.env.LINE_USER_ID; _requestBody.messages = [{'type': 'text','text': _message}]; _postData = JSON.stringify(_requestBody); } const pushMessage = () => { return new Promise(resolve => { //setMessageの使用を強制させる if(_postData === ''){ console.log('setMessageの使用が必要です'); return; } const headers= { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': `Bearer ${process.env.LINE_ACCESS_TOKEN}`, 'Content-Length':Buffer.byteLength(_postData) }; const _options = { hostname: 'api.line.me', port: 443, path: '/v2/bot/message/push', method: 'POST', headers: headers }; const req = https.request(_options, (res) => { console.log('statusCode:', res.statusCode); res.on('end', () => { resolve(true); }); }); req.on('error', (e) => { console.error(e); }); req.write(_postData); req.end(); }); } return { pushMessage: pushMessage, setMessage: setMessage } })();LINEから提供されている
bot-sdk
は使用せず、Node.js標準のhttps
モジュールを使用してエンドポイントにPOSTする方式でメッセージを送信する。外部ライブラリを使用した場合AWS Lambda
側でパッケージを使用する用の設定が別途必要。
- 私がハマった部分
メッセージボディが下記の場合は、400エラーを返す。
const requestBody = { 'to':'XXXXXXXXXXX', 'messages':{ 'type': 'text', 'text': 'Hello, world' } };公式ドキュメントを見ながら、何が間違っているのかわからず2,3時間これに消費した。
メッセージボディが下記の場合は、正しい。(配列形式になっているか)const requestBody = { 'to':'XXXXXXXXXXX', 'messages': [{ 'type': 'text', 'text': 'Hello, world' }] };参考:
LINEAPIへPOSTしてプッシュメッセージを送信する
テキストメッセージのJSONオブジェクトAWSのAPI権限周りの設定をする
- IAMユーザのポリシーの設定で
CloudWatchReadOnlyAccess
を付与する。- 認証情報をローカルに保存する
参考:
共有認証情報ファイルから Node.js に認証情報をロードする
~/.aws/credentialsファイルでプロファイルを切り替える
AWSのCloudWatchから概算請求金額を取得する
CloudWatchのgetMetricStatisticsメソッドを使って請求金額を取得する。
const AWS = require('aws-sdk'); const FechAWSBilling = (() => { const cw = new AWS.CloudWatch({region: 'us-east-1'}); const date = new Date const endTime = date.toISOString(); date.setDate(date.getDate() -1); const startTime = date.toISOString(); const params = { StartTime: startTime, EndTime: endTime, MetricName:'EstimatedCharges', Namespace:'AWS/Billing', Period:86400, Dimensions:[ { Name:'Currency', Value:'USD' } ], Statistics:['Maximum'] }; const getMetricStatistics = async () => { return new Promise(resolve => { cw.getMetricStatistics(params, (err, data) => { if (err){ console.log(err, err.stack); } resolve(data); }); }) } return{ getMetricStatistics: getMetricStatistics } })();ハマりやすい部分はパラメータの部分だと思われる。
初見では各プロパティが何を意味しているのか全く分からなかった。(今もよく分かっていない)const params = { StartTime:'2019-06-10T12:21:45.351Z', EndTime:'2019-06-12T12:21:45.351Z', MetricName:'EstimatedCharges', Namespace:'AWS/Billing', Period:86400, Dimensions:[ { Name:'Currency', Value:'USD' } ], Statistics:['Maximum'] };
MetricName
:アクセスするリソース名
CloudWatchメトリクス名
Namespace
:アクセスするサービス名
CloudWatch名前空間
Period:86400
:データの取得幅
1=1秒なので
60(秒) * 60(分) * 24(時間) = 86400(秒) = 1日
参考:
CloudWatchの概念
CloudWatchAPIドキュメント
Amazon Lambdaの登録を行う
毎日定時にCloudWatchのイベントを発生させ、それをトリガーにしてLambdaを起動させる。
毎日18時にイベントを発火する。(単位はGMT,日本時間との時差は9時間)
しかしこの公式通りやってもうまく設定できなかった。なぜ?
Rate または Cron を使用したスケジュール式
- Lambdaにソースコードを登録をする
const https = require('https'); const AWS = require('aws-sdk'); /** * LINEでPushメッセージを送信する */ const LINEMessage = (() => { let _postData = ''; let _message = ''; let _requestBody = new Object(); const setMessage = (data) => { _message = String(`今月のAWS概算請求金額は「${data}$」です。`); _requestBody.to = process.env.LINE_USER_ID; _requestBody.messages = [{'type': 'text','text': _message}]; _postData = JSON.stringify(_requestBody); } const pushMessage = () => { return new Promise(resolve => { //setMessageの使用を強制させる if(_postData === ''){ console.log('setMessageの使用が必要です'); return; } const headers= { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': `Bearer ${process.env.LINE_ACCESS_TOKEN}`, 'Content-Length':Buffer.byteLength(_postData) }; const _options = { hostname: 'api.line.me', port: 443, path: '/v2/bot/message/push', method: 'POST', headers: headers }; const req = https.request(_options, (res) => { console.log('statusCode:', res.statusCode); res.on('end', () => { resolve(true); }); }); req.on('error', (e) => { console.error(e); }); req.write(_postData); req.end(); }); } return { pushMessage: pushMessage, setMessage: setMessage } })(); /** * AWSから請求金額を取得する */ const FechAWSBilling = (() => { const cw = new AWS.CloudWatch({region: 'us-east-1'}); const date = new Date const endTime = date.toISOString(); date.setDate(date.getDate() -1); const startTime = date.toISOString(); const params = { StartTime: startTime, EndTime: endTime, MetricName:'EstimatedCharges', Namespace:'AWS/Billing', Period:86400, Dimensions:[ { Name:'Currency', Value:'USD' } ], Statistics:['Maximum'] }; const getMetricStatistics = async () => { return new Promise(resolve => { cw.getMetricStatistics(params, (err, data) => { if (err){ console.log(err, err.stack); } resolve(data); }); }) } return{ getMetricStatistics: getMetricStatistics } })(); exports.handler = async () => { const bill = await FechAWSBilling.getMetricStatistics(); LINEMessage.setMessage(bill.Datapoints[0].Maximum); const pushComplete = await LINEMessage.pushMessage(); if(pushComplete){ console.log('関数は正常に実行されました。'); return; } };
- 私がハマった部分
Nodeの非同期処理に注意する。 下記のコードは、Pushメッセージ送信が完了する前にLambda側での処理が終了してECONNRESET
エラーが発生する場合がある。Lambda外でのデバック時はエラーが発生しない分、原因調査に数時間消費した。lambda側でのテストでメッセージの受信ができること確認する。const bill = await FechAWSBilling.getMetricStatistics(); LINEMessage.setMessage(bill.Datapoints[0].Maximum); LINEMessage.pushMessage(); }下記のコードのようにPushメッセージが完了するようにする。
const bill = await FechAWSBilling.getMetricStatistics(); LINEMessage.setMessage(bill.Datapoints[0].Maximum); const pushComplete = await LINEMessage.pushMessage(); if(pushComplete){ console.log('関数は正常に実行されました。'); return; } }ソースコードの登録が完了したら、LINEで請求金額が通知されることを確認する。
以上です、ありがとうございました。
※AWSの公式ドキュメントの膨大さに気絶しそうになる。