20210901のNode.jsに関する記事は8件です。

JavaScriptを使った便利ツール

はじめに 自身のオリジナルアプリの作成により、JavaScriptを取り入れることで、「高機能なWebページ」を実装できることができました。 今回は、JavaScriptの「ライブラリ」と呼ばれる便利ツールのようなものを紹介したいと思います。 あくまで紹介のみですので、詳細や環境構築の説明は行いません。 実際の開発現場でよく使用するライブラリが中心であるため、今後開発を進める上での自身の備忘録としてここに記します。 私は初学者のため、間違いがございましたらお手数ですがご指摘いただけますと幸いです。 jQuery (ジェークエリー) 現在は下火傾向になるようですが、手軽に扱えることから今でも十分根強い人気があります。 特徴 ① HTMLの取得 & 追加削除がしやすい DOM操作(HTMLの取得や追加削除など)が容易で、コードを短く、かつ簡単に書くことができます。 ② Ajax処理の記述が簡単にできる 文字通りです。 Ajax(エイジャックス)という非同期通信技術の一つを用いるのですが、JavaScriptだけでサーバー通信を行い、 Ajax処理を容易に実行できるように設計されています。 ③ プラグインが豊富でさまざまな拡張が可能 プラグインすることにより、さまざまな処理や効果に対応できるようになります。 たくさんあるため、プラグインを導入すれば、自分が欲しい処理や効果を簡単に生み出すことができます。 無料で手に入るものもあるので、下記を参考にしてみてください。 【参考】 ④ ブラウザの影響を受けない JavaScriptは各ブラウザに搭載されたプログラムによって実行されます。 ChromeやSafari、FireFoxなどで、それぞれ微妙に対応可能なメソッドが異なります。 jQueryはブラウザ毎の記述の差異を自動的に吸収してくれるため、ブラウザの違いを意識せずプログラミングできます。 Node.js (ノード ・ ジェーエス) JavaScriptをサーバーサイドで動作させることができるプラットフォームです。 主に、リアルタイムで反応が必要なチャットアプリケーションの開発によく用いられています。 特徴 ① 接続を大量に処理できる イベント駆動型で、前の処理の完了を待たずに次の処理を開始します。 そのため、大量の同時接続が発生した場合でも処理を行うことが可能です。 ② 小規模な計算が速い JavaScript実行エンジンである「GoogleV8JavaScriptEngine」を採用しています。 そのため、非常に高速な動作が可能になり、小規模な計算であれば計算速度も速いです。 ③ リアルタイムの処理が速い シングルスレッドを採用しており、多くの接続があってもリアルタイムでレスポンスを返せるという特徴があります。 そのため、リアルタイムで動作するアプリケーションでの利用に適していると言えます。 ④ Webサーバーが軽量 Node.jsは非常に軽量な設計になっているため、複数の人が利用していても動作が重たくなることがありません。 そのため、チャットなどのサーバーであればNode.jsを利用すれば簡単に構築可能です。 TypeScript (タイプスクリプト) マイクロソフト社によって開発された、オープンソースのプログラミング言語になります。 Googleが開発するライブラリ(後述するAngular)にも導入されており、将来性がある言語の一つといえます。 特徴 ① JavaScriptで型の定義ができる let color: string = "gray" function log(message: string){ console.log(message) } 上記のような型を定義できる言語を静的型付け言語と言います。 ちなみに、Rubyや通常のavaScriptは動的型付け言語になります。 動的型付け言語は型を定義しなくて良いので、プログラムを記述するのは簡単ですが、エラーを起こす可能性もあります。 一方で、静的型付け言語は型を宣言して開発を行うので、事前に起こり得るエラーに対応できるというメリットがあります。 ② JavaScriptとの互換性が高い 他のAltJS同様に、記述したコードをコンパイルするとJavaScriptのコードに変換され、ライブラリ群を含め100%の互換性を持っています。 TypeScriptで拡張された機能は、コンパイラを通すことでJavaScriptのコードに変換されます。 現行のJavaScriptで記述されたプログラムはすべて、TypeScriptのプログラムとして実行が可能です。 React (リアクト) Facebookが開発をしたライブラリです。仮想DOMの概念によって、より早い高速なアプリケーション実装が実現できます。 また、AndroidやiOSのネイティブアプリケーション開発に、Reactを用いるフレームワークであるReact Nativeを使用することができます。 柔軟性も高いことから、最近大きく人気が増しています。 Vue.js (ビュー・ジェーエス) 近年JavaScriptのライブラリとして取り入れる現場が増えている人気のライブラリです。 Reactと同様に仮想DOMの概念があり、冗長な記述を減らして、HTMLやCSSを中心にしたWebアプリ開発が可能です。 Angular (アンギュラー) Google等が開発したライブラリです。 Angular自体もライブラリですが、さらにAngular内にも標準で多数のライブラリが用意されています。 したがって、外部の機能を使わずとも、公式の機能のみで十分な開発を行うことができます。 その機能の多さからAngularはフルスタックなライブラリと言われています。 また、Angularは推奨言語にJavaScriptではなく、TypeScriptを採用していることも特徴です。 過去にはAngularJSと呼ばれていましたが、大幅なアップデートを経てAngularと呼ばれるようになりました。 おわりに JavaScriptには、開発の目的に沿ってライブラリがいくつも存在することがわかりました。 そして、モダンな開発手法として、コンポーネント指向を取り入れているケースが多い傾向があることも理解しました。 特に仮想DOMの概念によって、より高速なアプリケーションが実現した背景があるからこそ、現在の形になっているのだと思いました。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINEオウム返しbotに、Node.jsでWebスクレイピングしたデータを追加する

Node.jsの勉強をする中で、LINE Messaging APIの公式チュートリアルに触れる機会がありました。 このチュートリアルはBotへの投稿に対して特定の返事をするだけですが、せっかくなので以下の機能を追加してみました。 テキストが投稿されたらオウム返しする。テキスト以外が投稿されたら「テキストを入力してください」と返す。 オウム返しするときに、ついでにWebスクレイピングしたデータも返す。 オウム返しBotの作成 LINE Messaging APIの公式チュートリアルの通りに進めたうえで、Webhookイベントオブジェクトのプロパティを確認し、テキストが送信されたときにはreq.body.events[0].message.text(送信されたテキストそのもの)を、それ以外が送信されたときは特定の文字列を返すようにしました。 下記で、コメントを入れているのが変更部分です。 index.js const https = require('https'); const express = require('express'); const app = express(); const PORT = process.env.PORT || 3000; const TOKEN = process.env.LINE_ACCESS_TOKEN; app.use(express.json()); app.use(express.urlencoded({ extended : true })); app.get("/", (req, res) => { res.sendStatus(200); }); app.post("/webhook", (req, res, next) => { res.send("HTTP POST request sent to the webhook URL!"); const replyMessages = []; if (req.body.events[0].type === "message"){ //req.body.events[0].message.typeがtextかどうかで分岐 if (req.body.events[0].message.type === "text"){ //textだった場合は、その内容をそのまま返す replyMessages.push({ "type" : "text", "text" : req.body.events[0].message.text }); }else{ //textじゃない場合は、固定の文字列を返す replyMessages.push({ "type" : "text", "text" : "テキストを入力してください" }) }; const dataString = JSON.stringify({ replyToken: req.body.events[0].replyToken, messages: replyMessages }); const headers = { "Content-Type": "application/json", "Authorization": "Bearer " + TOKEN }; const webhookOption = { "hostname": "api.line.me", "path": "/v2/bot/message/reply", "method": "POST", "headers": headers, "body": dataString } const request = https.request(webhookOption, (res) => { res.on("data", (d) => { process.stdout.write(d); }); }); request.on("error", (err) => { console.error(err); }); request.write(dataString); request.end(); } }); app.listen(PORT, () => { console.log(`Example app listening at http://localhost:${PORT}`); }); チュートリアルにあるとおりherokuにアップロードしてテストしたところ、きちんと動いてくれました! ちゃんと動くところを見れると嬉しい。 現時点でのフォルダ構成は、以下の通りです。シンプル。 heroku-sample-app └─node_modules │ └─express ├─.gitignore ├─index.js ├─package-lock.json └─package.json Webスクレイピングしたデータの追加 LINE Botが動いたのは良いのですが、それだけだとそんなに勉強にならないので、こちらのサイトから、プロ野球のDeNA戦データを引っ張ってくることにしました。 (ちなみに、Webスクレイピングを禁止するような文言が利用規約に含まれていないことは、先んじて確認しました。結構そのあたりが問題になるため、Webスクレイピングは要注意のようです。) Webスクレイピングを選んだのは、70%は趣味、30%は非同期処理(Promiseとかasync/awaitとか)を学べるかなと思ったのが理由です。 サイトのhtmlデータ取得にはaxios、取得したデータの解析にはcheerioを使いますので、これらのモジュールのインストールから進めていきます。 ①追加モジュールのインストール チュートリアル通りに作ったheroku-sample-app配下で、以下のモジュールを追加でインストールします。 > npm i axios cheerio > npm ls -depth 0 +-- axios@0.21.1 +-- cheerio@1.0.0-rc.10 `-- express@4.17.1 チュートリアルでインストールしたexpressに加え、2つモジュールが追加されたことが確認できました。 ②Webスクレイピング(getBaseballData.js)のコーディング Webスクレイピングするプログラムを書いていきます。 ちなみに、プロジェクトフォルダ配下の構成は以下の通りです。 heroku-sample-app └─node_modules │ ├─axios │ ├─cheerio │ └─express ├─.gitignore ├─getBaseballData.js ├─index.js ├─package-lock.json └─package.json まずは大枠を書きます。 getBaseballData.js const axios = require('axios'); const cheerio = require('cheerio'); //axiosでgetしたデータの解析が終わってからreturnするよう、async()とする const getBaseballData = async () => { //axiosでgetしたデータの解析が終わるのを待つよう、awaitを入れる const resData = await axios.get("https://baseball.yahoo.co.jp/npb/schedule/") .then((response) => { //axiosはPromiseを返すので、.thenで渡せる const html = response.data; const $ = cheerio.load(html); //cheerioでloadすることで、htmlタグやid,classで解析できる /*-- ここにLoadしたhtmlデータを解析する処理を入れていく --*/ }) .catch( e => console.error(e)); //axiosに対するcatch文 console.log(resData); //index.jsから非同期で呼び出すことを想定し、Promiseを返す return new Promise( resolve => resolve(resData));  }; //モジュールのエクスポート module.exports = getBaseballData; まずは大枠はこんな感じで書いてみました。 axiosとcheerioをロードした上で、getBaseballData関数を書いていきます。 自分なりに意識したポイントもはコメントで入れていますが、スクレイピングデータの解析が終わる前にreturnしないよう、async/awaitを使っています。 また、getBaseballDataはindex.jsから非同期で呼び出す想定なので、Promiseを返すようにします。 次に、Webサイトから取得したデータの解析を進めます。 DeNA戦情報を取得しますが、タイミングによって試合前・試合中・試合後のどれかがかわってくるので、それぞれを意識してデータをgDataにJSON形式で入れていきます。 getBaseballData.js const axios = require('axios'); const cheerio = require('cheerio'); const getBaseballData = async () => { const resData = await axios.get("https://baseball.yahoo.co.jp/npb/schedule/") .then((response) => { const html = response.data; const $ = cheerio.load(html); //ここから追加 const gData = {}; //DeNA戦情報を入れるJSON let homeTeam = ""; //DeNA戦を探すための変数、letで宣言 let awayTeam = ""; //DeNA戦を探すための変数、letで宣言 //$で、".bb-score__content"というidを持つタグを探索し、elemに返す。(その日に開催される試合情報が埋め込まれている) //eachを使って、DeNA戦を探す。 $('.bb-score__content' ).each((i, elem) => { //チーム名を取得し、homeTeam/awayTeamに代入 homeTeam = $('.bb-score__homeLogo' , elem).text() awayTeam = $('.bb-score__awayLogo' , elem).text() //DeNA戦が見つかったら、チーム名、先発(試合前のみ取得可能)、 //勝ち投手負け投手(試合後のみ取得可能)、 //スコア(試合中もしくは試合後に取得可能)、 //ステータス(試合が何回まで進んでいるか、 //もしくは試合終了しているか)をhtmlタグに付与されたidから取得 if(homeTeam === "DeNA" || awayTeam === "DeNA"){ gData.home = { "team" : homeTeam}; gData.away = { "team" : awayTeam}; gData.home.senpatsu = $('.bb-score__player--probable', '.bb-score__home' , elem).text(); gData.home.pitcher = $('.bb-score__player ', '.bb-score__home' , elem).text(); gData.away.senpatsu = $('.bb-score__player--probable', '.bb-score__away' , elem).text(); gData.away.pitcher = $('.bb-score__player' , '.bb-score__away' , elem).text(); gData.home.score = $('.bb-score__score--left' , '.bb-score__detail', elem).text(); gData.away.score = $('.bb-score__score--right' , '.bb-score__detail', elem).text(); gData.status = $('.bb-score__link' , '.bb-score__detail', elem).text(); }; }); //追加ここまで /*-- この後、ここでgDataの中身を使って返り値を作っていきます。 --*/ }) .catch( e => console.error(e)); console.log(resData); return new Promise( resolve => resolve(resData)); }; module.exports = getBaseballData; プロ野球は1日に複数の試合が開催されているので、その中でDeNA戦を探索し、idから返り値を組み立てるのに必要な情報を取得しています。 最後に、取得したデータから試合前・試合中・試合後のどれかを判別し、返り値を作ります。 (これで getBaseballData.jsは完成です!) getBaseballData.js const axios = require('axios'); const cheerio = require('cheerio'); const getBaseballData = async () => { const resData = await axios.get("https://baseball.yahoo.co.jp/npb/schedule/") .then((response) => { const html = response.data; const $ = cheerio.load(html); const gData = {}; let homeTeam = ""; let awayTeam = ""; $('.bb-score__content' ).each((i, elem) => { homeTeam = $('.bb-score__homeLogo' , elem).text() awayTeam = $('.bb-score__awayLogo' , elem).text() if(homeTeam === "DeNA" || awayTeam === "DeNA"){ gData.home = { "team" : homeTeam}; gData.away = { "team" : awayTeam}; gData.home.senpatsu = $('.bb-score__player--probable', '.bb-score__home' , elem).text(); gData.home.pitcher = $('.bb-score__player ', '.bb-score__home' , elem).text(); gData.away.senpatsu = $('.bb-score__player--probable', '.bb-score__away' , elem).text(); gData.away.pitcher = $('.bb-score__player' , '.bb-score__away' , elem).text(); gData.home.score = $('.bb-score__score--left' , '.bb-score__detail', elem).text(); gData.away.score = $('.bb-score__score--right' , '.bb-score__detail', elem).text(); gData.status = $('.bb-score__link' , '.bb-score__detail', elem).text(); }; }); //ここから追加 //statusを元に、試合の有無、有る場合の情報をまとめる関数を宣言。 const game_info = (home, away, status) =>{ if( status === "" ){ //試合が無い場合 return "本日のDeNA戦はありません"; }else{ if( status === "見どころ" ){ //試合前。予告先発を表示。 return "<対戦> " + home.team + "(" + home.senpatsu + ") vs " + away.team+ "(" + away.senpatsu + ")"; }else if( status === "試合終了" ){ //試合後。スコアと勝ち投手負け投手を表示。 return `<${status}>` + `${home.team}(${home.pitcher}) ${home.score}` + "-" + `${away.score} ${away.team}(${away.pitcher})`; }else { //試合中。何回まで進んでいるかとスコアを表示。 return `<${status}>` + `${home.team} ${home.score}` + "-" + `${away.score} ${away.team}`; } }; } //上記で宣言した関数を呼び出し、返り値を作成 const resData = game_info(gData.home, gData.away, gData.status); //return return resData; //追加ここまで }) .catch( e => console.error(e)); console.log(resData); return new Promise( resolve => resolve(resData)); }; module.exports = getBaseballData;` ③index.js側でのロード、herokuへのデプロイ getBaseballData.jsが無事に出来ました! あとは、これをindex.js側で読み込んでいきます。 変更箇所にはコメントを入れています。 index.js const https = require('https'); const express = require('express'); //getBaseballDataの読み込み const getBaseballData = require('./getBaseballData'); const app = express(); const PORT = process.env.PORT || 3000; const TOKEN = process.env.LINE_ACCESS_TOKEN; app.use(express.json()); app.use(express.urlencoded({ extended : true })); app.get("/", (req, res) => { res.sendStatus(200); }); //asyncの追加 app.post("/webhook", async (req, res, next) => { res.send("HTTP POST request sent to the webhook URL!"); const replyMessages = []; if (req.body.events[0].type === "message"){ if (req.body.events[0].message.type === "text"){ //awaitで非同期にgetBaseballDataを呼び出し。 const baseBallData = await getBaseballData().catch( e => { console.error(e); return '確認中・・・' }); replyMessages.push({ "type" : "text", "text" : req.body.events[0].message.text },{ //テキストを1文追加 "type" : "text", "text" : "本日のDeNA戦は・・・" },{ //getBaseballDataで取得したデータを追加 "type" : "text", "text" : baseBallData }); }else{ replyMessages.push({ "type" : "text", "text" : "テキストを入力してください" }) }; const dataString = JSON.stringify({ replyToken: req.body.events[0].replyToken, messages: replyMessages }); const headers = { "Content-Type": "application/json", "Authorization": "Bearer " + TOKEN }; const webhookOption = { "hostname": "api.line.me", "path": "/v2/bot/message/reply", "method": "POST", "headers": headers, "body": dataString } const request = https.request(webhookOption, (res) => { res.on("data", (d) => { process.stdout.write(d); }); }); request.on("error", (err) => { console.error(err); }); request.write(dataString); request.end(); } }); app.listen(PORT, () => { console.log(`Example app listening at http://localhost:${PORT}`); }); 下記のようにherokuにデプロイします。 > git add . > git commit -m "add baseballdata" > git push heroku master … … remote: Verifying deploy... done. 完成です。無事に動いていることが確認できました! 終わりに Webスクレイピングは思ったより簡単にできたのですが、Promiseやasync/awaitの動きを把握するのは相当難しかったです。もっと使いこなせるよう、引き続き頑張ろうと思います。 (あと、DeNA戦の情報を返してくれるのはいいけど、普通にWebサイトに情報を見にいった方が早いので、これだけだと実用面ではイマイチだと作ってから思いました・・・)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Browserify を試す(node.jsのrequireメソッドをブラウザで使う)

経緯 node-webrtc のサンプルコード解析して、サーバーサイドのnode.jsのJavaScriptなのか、ブラウザ用のJavaScriptなのか、混乱した node-webrtcについては別途めとめる予定 node-webrtcのサンプルでは、Browsersを用いて「node.js用のJavaScript」を「ブラウザ用のJavaScript」に変換していることがわかった node-webrtcコードを解析できるように、Browsers の最低限の知識を身に着けるためにまとめる 環境 Windows 10 node v16.8.0 npm 7.21.1 npm node-webrtcで使っているnode-fetchのバージョンに合わせている 詳細は下部にあるソースコードを参照ください +-- browserify-middleware@8.1.1 +-- browserify@17.0.0 +-- express@4.17.1 +-- node-fetch@2.6.1 `-- serve@12.0.0 Browserify とは requireメソッドをブラウザで使えるようにする魔法のようなテクノロジー Browsers don't have the require method defined, but Node.js does. With Browserify you can write code that uses require in the same way that you would use it in Node. node-fetch を node.jsで動作させる 実装コード 以下のHTTPリクエストを行うコードを node.js で実行 別途、http://localhost:5000/file.txt を用意しておく HTTPリクエストが成功して、ファイルが取得できることを確認 example.js const fetch = require('node-fetch'); async function Fetch() { const response = await fetch('http://localhost:5000/file.txt', {method: 'GET'}); const body = await response.text(); console.log(body); } Fetch(); 実行結果 HTTPのリクエストに成功 c:\BrowserifyExample>node example.js The test succeeded. require('node-fetch') をブラウザで動作させる パッケージ側にインストール Browserify を実行して、ブラウザで動作するJavaScriptに変換する node ./node_modules/browserify/bin/cmd.js example.js -o public/bundle.js 出力したファイルをHTMLで参照する <!DOCTYPE html> <html> <body> <h1>Open the Console on your browser.</h1> <script src="bundle.js"></script> </body> </html> node.sjのHTTPサーバーを起動してブラウザでFetchできていることを確認 node ./node_modules/serve/bin/serve.js ./public expressフレームワークと連携 browserify-middleware を使うと、サーバーサイドで、「require()のあるnode.js用のJavaScript」を、「ブラウザで処理できるJavaScript」に変換して、返却することができる 以下のようなコードで、express フレームワークと連携できる const browserify = require('browserify-middleware'); const express = require('express'); const app = express(); app.use(`/`, browserify("./example.js")); const server = app.listen(3000, () => { const address = server.address(); console.log(`http://localhost:${address.port}\n`); }); 変換されたJavaScriptのコード ソースコード
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambdaで値を返す方法まとめ

結論 handlerをasyncにして普通にreturnで99.9%くらいOK async + return 最も普通な方法です. exports.handler = async (event) => { const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; return response; }; async + returnでsetTimeoutする exports.handler = async (event) => { const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; setTimeout(() => { console.log("1000ms後"); }, 1000); setTimeout(() => { console.log("2000ms後"); }, 2000); return response; }; のような場合, 1回目の実行でreturnするまでが1000ms未満の場合, console.logには出力されず, ウォームスタートした場合に次回以降出力されます. 1回目 START RequestId: 4978d8d6-0550-46d2-ad36-769858d22fa3 Version: $LATEST END RequestId: 4978d8d6-0550-46d2-ad36-769858d22fa3 REPORT RequestId: 4978d8d6-0550-46d2-ad36-769858d22fa3 Duration: 2.50 ms Billed Duration: 3 ms Memory Size: 128 MB Max Memory Used: 85 MB Init Duration: 391.34 ms 2回目 (2秒以上開ける) START RequestId: e4dcdbb1-a150-43b4-9e80-c7a776837c16 Version: $LATEST 2021-09-01T03:17:49.058Z 4978d8d6-0550-46d2-ad36-769858d22fa3 INFO 1000ms後 2021-09-01T03:17:49.059Z 4978d8d6-0550-46d2-ad36-769858d22fa3 INFO 2000ms後 END RequestId: e4dcdbb1-a150-43b4-9e80-c7a776837c16 REPORT RequestId: e4dcdbb1-a150-43b4-9e80-c7a776837c16 Duration: 160.53 ms Billed Duration: 161 ms Memory Size: 128 MB Max Memory Used: 85 MB console.logで出力される際についてくる実行IDが, 前回のものになっていることがわかります. このsetTimeout内のconsole.logは, Lambda実行後かつhandler実行前に実行されます. (setTimeout(() => {console.log(new Date());}, 1000); してみましょう) 3回目実行直後に4回目 START RequestId: a87c55c4-73b6-402b-9b51-924d7ca7e178 Version: $LATEST END RequestId: a87c55c4-73b6-402b-9b51-924d7ca7e178 REPORT RequestId: a87c55c4-73b6-402b-9b51-924d7ca7e178 Duration: 1.14 ms Billed Duration: 2 ms Memory Size: 128 MB Max Memory Used: 86 MB ウォームスタートしていますが, 前回の実行のsetTimeoutが完了していないため, 何も出力されません. 5回目(2秒以上待機) START RequestId: 20217709-e518-48fe-ad60-0fe1c7dd627e Version: $LATEST 2021-09-01T03:19:13.790Z a87c55c4-73b6-402b-9b51-924d7ca7e178 INFO 1000ms後 2021-09-01T03:19:13.790Z a87c55c4-73b6-402b-9b51-924d7ca7e178 INFO 1000ms後 2021-09-01T03:19:13.790Z a87c55c4-73b6-402b-9b51-924d7ca7e178 INFO 2000ms後 2021-09-01T03:19:13.790Z a87c55c4-73b6-402b-9b51-924d7ca7e178 INFO 2000ms後 END RequestId: 20217709-e518-48fe-ad60-0fe1c7dd627e REPORT RequestId: 20217709-e518-48fe-ad60-0fe1c7dd627e Duration: 1.47 ms Billed Duration: 2 ms Memory Size: 128 MB Max Memory Used: 86 MB 溜まっていたsetTimeoutが処理された後にLambdaを実行したため, 一気に表示されました. 詳しくはLambdaのライフサイクルで 非async + Promise asyncで普通にreturnすれば値を返せるので, 当然Promiseを返すとうまくいきます. exports.handler = (event) => { const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; return new Promise((resolve) => { resolve(response) }); }; 非asyncな場合, 普通にreturnするだけだとnullになります. callback handlerの第3引数のcallbackで返す方法です. asyncかは問いません. exports.handler = async (event, context, callback) => { const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; callback(null, response); // callback後も実行される console.log("callback後"); setTimeout(() => { response.statusCode = 400; // ここの実行後にcallbackに入れた値が返るため, 400が出力される }, 1000); }; この場合, setTimeoutなどの非同期処理が全て終わってからcallbackに入れた値が返されます. 複数回callbackした場合 最も最初のcallbackのみ利用されます. exports.handler = async (event, context, callback) => { const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; setTimeout(() => { callback(null, response); }, 1000); callback(null, "hoge"); callback(null, "fuga"); }; hoge エラーの場合は第1引数 callback(new Error("era-")); context context.done, context.succeed, context.failが使えます. exports.handler = async (event, context, callback) => { const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; context.succeed(response); setTimeout(() => { console.log("fuga"); // この時点では実行されない (次回実行時にconsole.logされている) }, 1000); console.log("hogehoge"); // 実行される }; つまり, 非同期は後回しになるものの, succeed以降も処理が続く, returnとcallbackの中間の性質を持っています. ぶっちゃけ使いません. async + returnでいいです. callback, context, return全部並べてみる 普通に最初のものが処理されます. exports.handler = async (event, context, callback) => { context.succeed("hoge"); callback(null, "fuga"); setTimeout(() => { console.log("a"); // 次回までお預け }, 1000); return "hige"; }; hoge exports.handler = async (event, context, callback) => { callback(null, "fuga"); context.succeed("hoge"); setTimeout(() => { console.log("a"); // 実行される }, 1000); return "hige"; }; fuga どのみちこんな書き方したらコードレビューで凄まじい数のchange requestがやってくるはずなので, 普通書かないです. さいごに 他にも面白いのあったら教えてください?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Express.js ファイルアップロード (express-form-data 編)

はじめに Express.js でファイルアップロードする場合、multer と呼ばれるミドルウェアがしばしば使用されます。 しかし、ファイルアップロードに対応するミドルウェアは他にもいろいろあって express-form-data もその1つです。 express-form-data は単なるファイルアップロードのためのミドルウェアではなく、JavaScript の FormData オブジェクトを Express.js で扱うためのミドルウェアです。 Express.js では POST データはフォームから submit されたデータと JSON にのみ標準で対応しています。このため、Fetch API で FormData を POST してもリクエストボディが undefined になってしまい処理が続行できません。 そこで、express-form-data をミドルウェアにすることで、FormData も正しく扱えるようになります。 インストール express-form-data は次のようにしてインストールできます。 npm install express-form-data 単一ファイルのアップロード express-form-data では multer のように単一ファイル、複数ファイル、複数エレメントでコードを変える必要はなく、すべて req.files に保存されています。下の例では、input[type="file"] の name 属性が "file1" によりアップロードされたファイルを取得して、そのファイルの長い一時ファイル名を元の名前に変更するものです。 このケースでは、express-form-data をミドルウェアとして使用するので、モジュールの最初の方で次のようなコードを追加しておく必要があります。これは他の例でも同様です。 formData.parse() の引数は省略可能ですが、その場合、アップロードファイルの保存先が OS の一時フォルダになります。 const path = require('path'); const formData = require('express-form-data'); const updir = path.dirname(__dirname).replace(/\\/g, "/") + "/tmp"; // アップロード先のフォルダ router.use(formData.parse({uploadDir:updir, autoClean:true})); 下のコードが post ハンドラの例です。multer のような第二引数は不要です。 router.post('/single', (req, res) => { const path1 = req.files.file1.path; // アップロードされたファイルのフルパス名 const name = req.files.file1.name; // 元のファイル名 if (path1) { const dest = path.dirname(path1).replace(/\\/g, "/") + "/" + name; fs.renameSync(path1, dest); // 一時ファイル名を元のファイル名に変更する。 res.render('upload2', {message: dest + " にアップロードされました。"}); } else { res.render('upload2', {message: "エラー:アップロードできませんでした。"}); } }); このときのフォームは次のような感じです。 <form id="form1" method="POST" enctype="multipart/form-data" action="/upload2/single"> <fieldset class="form-group"> <label for="file1">アップロードファイル </label> <input type="file" id="file1" name="file1" class="form-control" /> </fieldset> <fieldset class="form-group"> <input type="submit" class="btn btn-primary" value=" 送信 " /> </fieldset> <br /> </form> <br /> <p class="message" id="message"><%= message %></p> 複数ファイルのアップロード express-form-data を使用する場合、アップロードファイルが単一か複数かの違いはなく req.files にそのデータが入っています。この例では input[type="file"] の name 属性が "file1" の場合、アップロードファイルの情報が req.files.file1 に入っていますが、これが配列になっており、そこからアップロードファイルの情報を取り出します。 (注意) express-form-data の初期化が必要。単一ファイルのアップロード の説明参照。 router.post('/multiple', (req, res) => { const n = req.files.files1.length; // アップロードされたファイル数 for (let i = 0; i < n; i++) { let src = req.files.files1[i].path.replace(/\\/g, "/"); let dest = path.dirname(req.files.files1[i].path).replace(/\\/g, "/") + "/" + req.files.files1[i].name; fs.renameSync(src, dest); // 長い一時ファイル名から元の名前に変更する。 } if (n > 0) { res.render('upload2Multi', {message: n + " 個のファイルがアップロードされました。"}); } else { res.render('upload2Multi', {message: "エラー:アップロードできませんでした。"}); } }); この例でのフォームは次のようになっています。 <form id="form1" method="POST" enctype="multipart/form-data" action="/upload2/multiple"> <fieldset class="form-group"> <label for="file1">アップロードファイル </label> <input type="file" id="files1" name="files1" class="form-control" multiple /> </fieldset> <fieldset class="form-group"> <input type="submit" class="btn btn-primary" value=" 送信 " /> </fieldset> <br /> </form> <br /> <p class="message" id="message"><%= message %></p> Fetch API と FormData を使用してのアップロード express-form-data を使うと FormData オブジェクトとして input[type="file"] を含むフォームを丸ごとサーバへ送信できるので、クライアント側のコードが簡単になります。 (注意) express-form-data の初期化が必要。単一ファイルのアップロード の説明参照。 router.post('/formdata', (req, res) => { const path1 = req.files.file1.path.replace(/\\/g, "/"); // アップロードされたファイルのフルパス名 const name = req.files.file1.name; // 元のファイル名 if (path1) { const dest = path.dirname(path1).replace(/\\/g, "/") + "/" + name; fs.renameSync(path1, dest); // 一時ファイル名を元のファイル名に変更する。 res.send(dest + " にアップロードされました。"); } else { res.send("エラー:アップロードできませんでした。"); } }); 次のコードは、input[type="file"] エレメントを含むフォームの例です。アップロードの結果は id="message" で指定される p タグの内部文字列として表示されます。 <form id="form1" method="POST" enctype="multipart/form-data"> <fieldset class="form-group"> <label for="file1">アップロードファイル </label> <input type="file" id="file1" name="file1" class="form-control" /> </fieldset> <fieldset class="form-group"> <input type="button" class="btn btn-primary" value=" 送信 " onclick="javascript:submitClick()" /> </fieldset> <br /> </form> <br /> <p class="message" id="message"></p> 次がクライアント側のコードで、送信ボタンをクリックしたときのハンドラです。fetch 関数のパラメータが FormData オブジェクトなのですっきりします。 function element(id) { return document.getElementById(id); } function submitClick() { const formData = new FormData(form1); fetch('/upload2/formdata', {method:"POST", body:formData}) .then(res => res.text()) .then(text => element('message').innerText = text) .catch(err => element('message').innerHTML = err.message); } FormData を使った複数エレメントのファイルアップロード この例は複数 input[type="file"] エレメント (name="file1", name="file2") を持つフォームからのファイルアップロードのコードです。基本的に単一ファイルの場合と同じで、name="file1", name="file2" について同じコードを書くだけです。 router.post('/formdata2', (req, res) => { const path1 = req.files.file1.path.replace(/\\/g, "/"); // アップロードされたファイルのフルパス名 (1) const path2 = req.files.file2.path.replace(/\\/g, "/"); // アップロードされたファイルのフルパス名 (2) const name1 = req.files.file1.name; // 元のファイル名 const name2 = req.files.file2.name; // 元のファイル名 if (path1) { var dest1 = path.dirname(path1).replace(/\\/g, "/") + "/" + name1; fs.renameSync(path1, dest1); // 一時ファイル名を元のファイル名に変更する。 if (path2) { var dest2 = path.dirname(path2).replace(/\\/g, "/") + "/" + name2; fs.renameSync(path2, dest2); // 一時ファイル名を元のファイル名に変更する。 } res.send(`"${dest1}", "${dest2}" にアップロードされました。`); } else { res.send("エラー:アップロードできませんでした。"); } }); 次のコードは、この場合のフォームの例です。 <form id="form1" method="POST" enctype="multipart/form-data"> <fieldset class="form-group"> <label for="file1">アップロードファイル (1)</label> <input type="file" id="file1" name="file1" class="form-control" /> </fieldset> <fieldset class="form-group"> <label for="file1">アップロードファイル (2)</label> <input type="file" id="file2" name="file2" class="form-control" /> </fieldset> <fieldset class="form-group"> <input type="button" class="btn btn-primary" value=" 送信 " onclick="javascript:submitClick()" /> </fieldset> <br /> </form> <br /> <p class="message" id="message"></p> 次のコードは、クライアント側の JavaScript コードの例です。 function element(id) { return document.getElementById(id); } // 送信ボタンがクリックされたとき function submitClick() { const formData = new FormData(form1); // form1 を元に FormData オブジェクトを作成する。 fetch('/upload2/formdata2', {method:"POST", body:formData}) .then(res => res.text()) .then(text => element('message').innerText = text) .catch(err => element('message').innerHTML = err.message); } express-form-data を使うと multer より簡単にファイルアップロードを行えます。特に、Fetch API を使う場合は、FormData が使えるので非常に簡単になります。 終わり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Express.js ファイルアップロード (multer 編)

はじめに multer はファイルアップロードでよく使われる Express.js ミドルウェアです。サンプルや関連記事も多くて安定した人気です。 multer ではファイルアップロードに関して次のことが可能です。 単一ファイルのアップロード 単一エレメントからの複数ファイルのアップロード 複数エレメントからの複数ファイルのアップロード npmjs.com のサンプルや説明は簡単な物しかなかったので、上の3つのケースについて簡単に試してみました。 その前に multer の基本ですが次のようにします。 multer モジュールをロードする。 const multer = require('multer'); multer オブジェクトを初期化する。 const upload = multer({dest:updir}); // updir はアップロード先のフォルダ(フルパス)。省略した場合は、OS の一時フォルダが使用される。 リクエストハンドラの第2引数には multer オブジェクトのオプションを設定する。 router.post('/single', upload.single('file1'), (req, res) => { .. }); multer の初期化 multer を使う場合、アップロード先のフォルダを指定しておく必要があります。ただし、これは省略可能で省略すると OS で決まる一時フォルダが使用されます。 const path = require('path'); const multer = require('multer'); const updir = path.dirname(__dirname).replace(/\\/g, "/") + "/tmp"; // アプリケーションフォルダのサブディレクトリ "./tmp" をアップロード先にしている。 const upload = multer({dest:updir}); 単一ファイルのアップロード フォームに 1 つの input[type="file"] エレメントがあり、multiple 属性がない場合の例です。POST ハンドラの第二引数に upload.single('file1') を指定します。ただし、"file1" は input[type="file"] エレメントの name 属性です。 router.post('/single', upload.single('file1'), (req, res) => { const path = req.file.path.replace(/\\/g, "/"); if (path) { const dest = updir + "/" + req.file.originalname; fs.renameSync(path, dest); // 長い一時ファイル名を元のファイル名にリネームする。 res.render('upload', {message: `${dest} にアップロードされました。`}); } else { res.render('upload', {message: "エラー:アップロードできませんでした。"}); } }); この時のフォームは次のような感じなります。 <form id="form1" method="POST" enctype="multipart/form-data" action="/upload/single"> <fieldset class="form-group"> <label for="file1">アップロードファイル (1)</label> <input type="file" id="file1" name="file1" class="form-control" /> </fieldset> <fieldset class="form-group"> <input type="submit" id="submitButton" name="submitButton" class="btn btn-primary" value=" 送信 " /> </fieldset> </form> <p class="message" id="message"><%= message %></p> 複数ファイルのアップロード 複数ファイルをアップロードする場合は、post ハンドラの第二引数に upload.array('file1', MAXFILES) が必要です。'file1' はフォームの input[type="file"] の name 属性、MAXFILES はアップロードできるファイルの最大数です。upload は multer オブジェクトです。 const MAXFILES = 3; router.post('/multiple', upload.array('file1', MAXFILES), (req, res) => { const n = req.files.length; try { for (let i = 0; i < n; i++) { const path = req.files[i].path.replace(/\\/g, "/"); const dest = updir + "/" + req.files[i].originalname; fs.renameSync(path, dest); // 長い一時ファイル名を元のファイル名にリネームする。 } res.render('upload', {message: `${n} 個のファイルがアップロードされました。`}); } catch (err) { res.render('upload', {message: "エラー:アップロードできませんでした。"}); } }); この時のフォームは次のような感じなります。input[type="file"] タグに multiple 属性が付与されています。multiple 属性を設定すると、ファイル選択ダイアログで Ctrl キーを押しながら複数ファイルを選択できます。 <form id="form1" method="POST" enctype="multipart/form-data" action="/upload/multiple"> <fieldset class="form-group"> <label for="file1">アップロードファイル (1)</label> <input type="file" id="file1" name="file1" class="form-control" multiple /> </fieldset> <fieldset class="form-group"> <input type="submit" id="submitButton" name="submitButton" class="btn btn-primary" value=" 送信 " /> </fieldset> </form> <p class="message" id="message"><%= message %></p> 複数エレメントからのアップロード フォームには複数の input[type="file"] がある場合がありますが、そういう場合は post ハンドラの第二引数に upload.fields(namedOption) が必要です。upload は multer オブジェクトです。namedOption は、複数 input[type="file"] の指定とアップロードできるファイルの最大数の一覧です。 const MAXFILES = 3; const namedOption = [ {'name':'file1', maxCount:1}, {'name':'file2', maxCount:MAXFILES} ]; router.post('/named', upload.fields(namedOption), (req, res) => { // file1 const path1 = req.files['file1'][0].path.replace(/\\/g, "/"); if (path1) { const dest1 = updir + "/" + req.files['file1'][0].originalname; fs.renameSync(path1, dest1); // 長い一時ファイル名を元のファイル名にリネームする。 } else { res.render('upload', {message: "エラー:アップロードできませんでした。(file1)"}); return; } // file2 const n = req.files['file2'].length; for (let i = 0; i < n; i++) { let path2 = req.files['file2'][i].path.replace(/\\/g, "/"); let dest2 = updir + "/" + req.files['file2'][i].originalname; fs.renameSync(path2, dest2); } res.render('upload', {message: `${n+1} 個のファイルがアップロードされました。`}); }); この時のフォームは次のような感じなります。input[type="file"] (name="file2") タグに multiple 属性が付与されています。multiple 属性を設定すると、ファイル選択ダイアログで Ctrl キーを押しながら複数ファイルを選択できます。 <form id="form1" method="POST" enctype="multipart/form-data" action="/upload/multiple"> <fieldset class="form-group"> <label for="file1">アップロードファイル (1)</label> <input type="file" id="file1" name="file1" class="form-control" /> </fieldset> <fieldset class="form-group"> <label for="file1">アップロードファイル (2)</label> <input type="file" id="file2" name="file2" class="form-control" multiple /> </fieldset> <fieldset class="form-group"> <input type="submit" id="submitButton" name="submitButton" class="btn btn-primary" value=" 送信 " /> </fieldset> </form> <p class="message" id="message"><%= message %></p> ファイルアップロード express-form-data 編もあります。 終わり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js で Excel ファイルをパースしてデータ抽出するときの備忘録

覚えておきたいことだけ書く。 リファレンス セルの指定方法 'A1', 'A2' と指定するのが基本。 隣のセルとかを指定するには、セルをエンコードして操作する。 Cell and cell address manipulation: format_cell generates the text value for a cell (using number formats). encode_row / decode_row converts between 0-indexed rows and 1-indexed rows. encode_col / decode_col converts between 0-indexed columns and column names. encode_cell / decode_cell converts cell addresses. encode_range / decode_range converts cell ranges. 値の取得 セルオブジェクトには、v(raw value), w(formatted value), t(type) の値がある。 数字は変な値になる。日付とかはvで取れない。 なので、基本はwで取得する。 参考 https://www.kwbtblog.com/entry/2019/09/27/122318 https://photo-tea.com/p/17/excel-parse-javascript-node/#%E3%82%A8%E3%82%AF%E3%82%BB%E3%83%AB%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E4%B8%AD%E8%BA%AB%E3%82%92nodejs%E3%81%A7%E8%A7%A3%E6%9E%90 https://qiita.com/indometacin/items/020513f7801a040dab33
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCodeの拡張機能を作ってみる その1 導入編

VSCodeの拡張機能を作ってみよう モチベーション 仕事をする上で欠かせないものとなりつつあるVSCode。 自分なりにカスタマイズして使ったりもしているけれども。 拡張機能を利用すればどんなことまで出来るのかという事を知っておくと、 より使いこなす事が出来るかなと思いチュートリアルを試してみる事に。 参考 前準備 Node.jsのインストール チュートリアルに入る前にnpmを叩けないといけないので、Node.jsをインストール Node.js公式からLTS版をダウンロード https://nodejs.org/ja/ msi を実行してインストール コマンドの実行確認 >node -v v14.17.5 >npm -v 6.14.14 作業フォルダの作成 今回はWindows環境で作業するので、以下のフォルダを作業フォルダとして準備 C:\code_extention Your First Extension npm で extention の開発に必要な Yeoman と VS Code Extension Generator のインストールを実施 C:\code_extention>npm install -g yo generator-code しばらく待ってインストールが完了したらVSCode Extension generatorを起動 対話式の質問に対して返答して自動生成の実行を待つ C:\code_extention>yo code ? ========================================================================== We're constantly looking for ways to make yo better! May we anonymously report usage statistics to improve the tool over time? More info: https://github.com/yeoman/insight & http://yeoman.io ========================================================================== Yes _-----_ ╭──────────────────────────╮ | | │ Welcome to the Visual │ |--(o)--| │ Studio Code Extension │ `---------´ │ generator! │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.'__ ´ ` |° ´ Y ` ? What type of extension do you want to create? New Extension (TypeScript) ? What's the name of your extension? HelloWorld ? What's the identifier of your extension? helloworld ? What's the description of your extension? ? Initialize a git repository? Yes ? Bundle the source code with webpack? No ? Which package manager to use? npm 終了すると、idで指定したフォルダ名に以下画像のようなファイルが出来上がる 生成されたフォルダをVSCodeで開く C:\code_extention>code ./helloworld 立ち上がり次第、F5を押すとExtentionが実行される。 この時、F5を押したら新しいウィンドウが立ち上がるので、その中でCtrl+Shift+Pを押して Hello と入力すれば、作成したExtentionが出てくる。 ※ 元のウィンドウで、あれ?動かないとしばらくやってしまった ↓ F5押下 ↓ 新しいWindowでCtrl+Shift+Pを押下し、Helloと入力し実行 ↓ 実行すると、右下にインフォメーションが表示される。 Githubへ登録 折角なので、作成したプロジェクトをGithubに登録 ローカルリポジトリへのcommitはあらかじめしておく。 Githubで新規プロジェクトを作成 プロジェクトのリモートリポジトリを追加してPush C:\code_extention\helloworld>git remote add origin https://github.com/yakato-jun/vscode-extention-example-helloworld.git C:\code_extention\helloworld>git push --set-upstream origin master 登録したGithubリポジトリは以下。 拡張機能コードの変更 (続く)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む