kei0425tan’s blog

技術的なことを主に

nvmを入れたらscpが動かなくなったときの対処

久しぶりにホスト間でファイルをコピーしようとして、scpを使ってみたら、うまくコピーできなくなっていました。

どういうこと?

scpを実行すると謎?のメッセージが出力されてどうもコピーされていないようです。

$ scp hostname:/home/user/filename .
Now using node v8.2.1 (npm v5.3.0)
$

進捗がでないし、ログインしたときに表示されるnvmのメッセージが表示されています。

どうして?

nvmをインストールするときに、一般的に以下のコマンドを利用します。

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash

これを実行すると、.bashrcに自動的に以下が追加されます。

# nvm
if [ -s ~/.nvm/nvm.sh ] ; then
source ~/.nvm/nvm.sh
nvm use default
fi

一方、scpを実行すると、内部でシェルが起動されるため、.bashrcを実行します。
そのため、nvm use defaultが実行され、「Now using node v8.2.1 (npm v5.3.0)」が表示されてしまいます。
SCPはシェル実行時に標準出力があるとそれを表示して動作を停止してしまう仕様のようです。

どうすれば?

ようするに、シェル実行時に標準出力がなくなればよいのです。

標準出力しない

「Now using node v8.2.1 (npm v5.3.0)」を出力するのは、nvm use defaultです。なので以下のようにしましょう。

# nvm
if [ -s ~/.nvm/nvm.sh ] ; then
source ~/.nvm/nvm.sh
nvm use default > /dev/null
fi

.bash_profileに移す

上記でもよいのですが、そうすると普通にログインした時に、nodeのバージョンが表示されません。
別に表示されなくてもほとんど困らないのですが、せっかく今まで表示されていたので普通に表示させたいですよね。
なので、該当箇所を.bash_profileにそっくり移動してあげましょう。



※ この前ネットサーフィンしてみたら、このフォーマットで書くと分かりやすいとのことだったのでパクってみました!

nodejsで自作別リポジトリ(gitlab/github)をモジュール化して利用するには?

プログラムを作っていて、この機能をあのプログラムでも利用したいなぁ。。。といったことはよくあるかと思います。

そういうときには、コピペしてしまうと整合性が取れなくなってあとで困ることになります。
なので、うまくgitのリポジトリで取り込めないかといろいろ調べてみたのですが、npmはちゃんと対応していました。

npm package.json 日本語版 取扱説明書
こちらに依存性のGit URL参照に記述がありました。

まとめると以下の手順になります。

モジュール側の設定

package.jsonの編集

今まであんまり意識していませんでしたが、以下の項目が大切になります。

  • name

→ パッケージ名になります。ここで記述した名前を指定してrequireで読み込みます。

  • private

→ trueを指定しておくと、npmに間違えて登録したりしません。

  • main

→ requireで実行するソースを指定します。
最後(じゃなくてもいいけど)に「module.exports = ほげほげ」を記述します。
利用するプログラムでは、xxx = require(yyyy)とすることにより、モジュールで記述したほげほげをxxxで参照できます。

  • version

→ バージョンを記述しておかないとわかりにくいでしょう。

gitlab/githubにpush

普通にpushして他からcloneできるようにしておきましょう。

利用側の設定

npm install
% npm install git+ssh://git@gitlab.com:ユーザ名/リポジトリ名.git --save

これだけです。
gitlabのsshに記述してある

git@gitlab.com:ユーザ名/リポジトリ名.git

をgit+ssh://の後ろにそのまま指定すればよいです。

githubの場合はもう少し簡単で以下でOKです。

% npm install git://github.com/ユーザ名/リポジトリ名.git --save

もちろん、gitlabと同様に指定しても問題ありません。

モジュール側を修正する場合

ファイルを修正後、package.jsonのversionを変更します。
その後、利用側で、「npm install パッケージ名」を実行します。

おわりに

モジュールする場合、共通部分を抜き出して行うのが正しい方法かとは思います。
しかし、簡単な処理の場合は全体をモジュール化して読み込んでしまうのもありかと思います。

mapでasync/awaitを利用する

久しぶりに、JavaScriptのasync/awaitの話です。

(node v7.9.0で記載しています。)

webをスクレイピングする時など、リストを元に非同期処理を行いたいことがあるかと思います。

その際に、mapで呼び出す関数をasync指定で定義しても、かえってくるのはPromiseオブジェクトになり、扱いにくいです。

ちゃんと実行を待ち、中身を取り出すには、基本に立ち返り、Promise.allを利用すればうまく取り出せます。


gist0b1e7b16be289ce69bfe566797afaff6

結果はこちら

[ Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> } ]

実行中にも関わらず、mainが終了してしまっています。



gist182cab9bbc634255a0f3e8b70d991f19

結果はこちら

[ { filename: 'data/all1.txt', length: 19846 },
  { filename: 'data/all2.txt', length: 17212 },
  { filename: 'data/all3.txt', length: 21116 },
  { filename: 'data/all4.txt', length: 13751 } ]

ちゃんと中身が確認できました!

以前の記事はこちら
kei0425tan.hatenablog.com
kei0425tan.hatenablog.com

mochaでasync/awaitが使えるようになってました!

node v7.0からasync/awaitが実装されていましたが、起動オプションに--harmony-async-awaitの指定が必要でした。

しかし、v7.6からJavaScriptエンジンのV8がバージョン5.5に上がったため、起動オプションの指定が不要になりました。

その前に、nodeでの非同期処理の扱いについて簡単に解説

コールバック時代

元々ノンブロッキングでイベントドリブンで記述するのがnodeの特徴のため、読み込みイベントにコールバックを登録して処理を記述します。
どこに処理を記述するのかわかりにくいですね。
ただし、chunk形式のため、非常に大きな入力でも対応できるのがメリットです。

コールバック版標準入力読み込み

Promise時代

コールバックを利用すると、ネストがどんどん深くなってしまう欠点がありました。
(複数ファイルを読み込むだけで、単純にそのファイル数だけネストしてしまったりなど)
まあ、無名関数にせずに全部通常の関数化して記述すればネストについては何とかなるのですが、手軽に記述できるとは言い難いです。

そこで、Promiseという仕組みを偉い人が考えました。
(元はデザインパターンのひとつのFutureだそうです。)
future - Wikipedia

ようは、コールバックの中にコールバックを記述するのではなく、チェインして連続して記述できる仕組みです。
(めっちゃ大雑把だけどゆるしてね)

Promise版標準入力読み込み

Promise化の方法

関数の中身を以下で記述します。

function hoge() {
  return new Promise((resolve, reject) => {
    hogehoge(param, (err, data) {
      if (err) {
        // 失敗した場合
        reject(err);
      }
      else {
        // 成功した場合
        resolve(data);
      }
    }
  });
}

resolveは必須です。rejectはなくてもかまいません。

async/await時代

Promiseを経て、コールバック地獄からは免れるようになったのですが、まだ見た目が慣れないことが多いです。
さらに、無名関数が多数書かれることによる変数のスコープの問題や、例外の問題などもあります。
そこで、async/awaitを使うことにより、通常のプログラムと同じく上から下へ記述できるようになり、コールバック関数も不要になり、ブロッキングもされないようになりました。

async/await版標準入力読み込み
paiza.ioのnodeのバージョンがv6のため、async/awaitに未対応なので、gistです。

async/await 標準入力読み込み

async/await化の方法

まず、Promise化を行います。
その後、呼び出し側の関数定義に、asyncを付けて、呼び出す関数は結果を待つ場合には、awaitをつけます。
非同期で呼び出したい場合には、awaitを付けなければ従来通り非同期になります。

ユニットテストではどうする?

そんなわけで、async/awaitを利用すると非常に記述しやすい&読みやすくなります。
でも、ユニットテストを書くときはどうするのでしょう??

答えは簡単。普通に無名関数を記述する箇所をasync function() にすれば大丈夫でした。
rejectされた場合もちゃんと例外が発生します。
ただし、assert.throwsはasyncつけてもうまく動作しませんでした。
仕方がないので、try/catchで検査しましょう。


mocha async/await テスト

結果は以下。
case2は例外が発生したケース
case3はassert.throwsを使って補足できなかったケース
なので、失敗します。

  test
    ✓ case1
    1) case2
    2) case3
(node:11613) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: ENOENT: no such file or directory, open './test/nofile'
(node:11613) 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.
    ✓ case4


  2 passing (24ms)
  2 failing

  1) test case2:
     Error: ENOENT: no such file or directory, open './test/nofile'
  

  2) test case3:
     AssertionError: Missing expected exception (Error)..
      at _throws (assert.js:345:5)
      at Function.throws (assert.js:369:3)
      at Context.<anonymous> (test/await-test.js:23:16)


claudia.jsでslack custom commandsを作る

kei0425tan.hatenablog.com

というわけで、まずはslackを使いこなせないと始まりません。

手始めに、custom commandsを使ってみます。

こちらが公式ページ(英語です)
api.slack.com

その前に、ここまでできてる前提で話しますね。
kei0425tan.hatenablog.com

slackの設定

というわけで、AWS lambdaで、web APIは使いこなせる??ようになっているはずです。
なので、slackでの設定を行います。

まずは、slackのチーム名の隣の下向き▼を押して、メニューを出して、「Apps & integrations」をクリックします。

面倒だったら、以下をクリックで!
slack.com

外部連携は基本的にいつもこのページからできるはずです。

その後、右上の「Build」をクリックし、「Something just for my team」をクリックします。

すると、以下の4つが表示されます。

  • Incoming WebHooks

→ slackにメッセージを表示する。

  • Bots

→ いわゆるBots

  • Slash Commands

→ /で始まるコマンド

  • Outgoing WebHooks

→ slackの表示されたメッセージを外部に渡す。

今回は、Slash Commandsを作成するので、そちらを選択します。

そうすると、コマンド名を入力する画面になるので、好きなコマンド名を入力します。

すると、まずPOSTデータのサンプルが表示されます。

token=RIKqnGNDN6r2yx01mOkIGgwe
team_id=T0001
team_domain=example
channel_id=C2147483705
channel_name=test
user_id=U2147483697
user_name=Steve
command=/weather
text=94070
response_url=https://hooks.slack.com/commands/1234/5678

それぞれの意味は以下です。

  • token → slackの認識用のトークン。受け取ったらまずここをチェックして違ったら弾くこと。
  • team_id → チームID。チームドメインは変更可能だが、team_idはずっとユニークで変更できないはず。
  • team_domain → チームドメイン。urlの最初。
  • channel_id → チャンネルID。チャンネル名は変更可能だが、こちらはユニーク。
  • channel_name → チャンネル名。
  • user_id → ユーザID。こちらもユニーク
  • user_name → ユーザ名
  • command → 入力されたコマンド(/つき)
  • text → 入力されたコマンドの引数。日本語も入ります。入力通りに受け取れます。
  • response_url → 処理に時間が掛かるときに利用するレスポンス用URL

次に、上から順に以下を設定していきます。

  • URL → 前回作成したURL
  • Method → POSTのまま
  • Token → そのまま。メモっておきましょう。
  • Customize Name → 表示するコマンド名。
  • Customize Icon → アイコンの変更ができます。
  • Preview Message → 上のCustomize NameとCustomize Iconを適用したプレビュー
  • Autocomplete help text → こちらを設定しておくと補完が効いてヘルプが見えます。
  • Escape channels, users, and links → ここを設定しておくと、POSTデータのtextが拡張されて、一緒にユーザIDなどが来ます。
  • Descriptive Label → なんだろう??知ってる人教えてください。

というわけで、大事なのはURLだけですね。

ここでは仮に、
コマンド名は/test
claudiaのpathも
https://yfw8aqrorg.execute-api.ap-northeast-1.amazonaws.com/latest/test
にしましょう。

それで、最後までいったら「Save Integration」をクリックします。
画面は遷移しませんが保存されます。

AWSの設定


とりあえず、前回のスクリプトに以下を追加。トークンは上のトークン入れてくださいね。

api.post('/test', function (request) {
    if (request.post.token != 'トークン') {
        return {message:'token error'};
    }
    
    return {
        "response_type": "in_channel",
        "text": "It's 80 degrees right now.",
        "attachments": [
            {
                "text":"Partly cloudy today and tomorrow"
            }
        ]
    };
    
});

APIのレスポンスはJSONで返します。

  • response_type → "in_channnel"を指定するとチャンネル全員が見えます。"ephemeral"を指定するとコマンドを発行した人のみに見えます。デフォルトでは、"ephemeral"を指定したのと同じです。
  • text → 表示するメッセージです。
  • attachments → 追加情報。引用みたくみえます。

さあ、ここまでできれば、あとは自由自在にコマンドを作れますね。

レッツコーディング!

paiza D060:【キャンペーン問題】AボタンとBボタン

キャンペーンをやっていたので久しぶりにpaizaの問題を解きました。

paiza.jp

今回は題では問題の見当がつきませんでしたが、流し読みでチョー簡単なことが分かったので速攻で提出しましたが
それでも、1分以上かかってしまいました。

やっぱり、タイムを気にするのであれば、提出前のチェックはしちゃいけませんね。

ちなみにキャンペーンはこんな感じ
paiza.jp
https://paiza.jp/assets/campaign/amazon_gift_Reward_03-82821d3aa15460642db01e6789823dd5.png

アマゾンギフト
1万円分×2名様
5000円分×5名様
1000円分×5名様
500円分×10名様

あたったらいいなー

javascript の Async/Await を利用した場合のエラーハンドリングについて

普段趣味ではjavascriptでプログラムを書くことが多いのですが、callbackが面倒なので、ずっとPromiseを使っていました。

thenって書けばインデントも増えないし、ブロックごとに新規に変数作ったりしていいじゃんって思っていたのですが、、、

最近作っているアプリケーションで相手のサーバのせいで、通信のエラーが頻発するようになり、やっぱりエラーハンドリングがちょっとつらいので、node v8からリリース予定のAsync/Awaitについて調べてみました。

nodeで試す場合は、v7にして、さらにオプション --harmony-async-awaitを付けてください。
ブラウザの場合は、そのまま貼り付ければ実行できます。


この結果を見ると、await中の例外は残念ながらキャッチしてくれませんでした。

よって、awaitで呼び出すメソッドは、確実に例外処理を行う必要があります。(try catchして、rejectを呼び出すなど)
一方、awaitを利用するほうも、rejectを確実に処理をする必要があります。(try catchして、リカバリなどを行う)

まあ、もともとコールバックで行っていた場合は例外が飛ばなかったし、Promiseで行っていた場合も、catchの付け方が難しかったので、使い慣れたtry catchでエラーハンドリングができるようになるのは、大きなメリットだと思います。