20200119のNode.jsに関する記事は7件です。

Node.js install for mac

何からNode.jsを入手したか忘れないためのメモ。

  • 公式からLTS版mac用のinstallerをダンロードする
  • installerを起動し、各設問をデフォルトのまま進めてinstallする
    • スクリーンショット 2020-01-19 23.05.14.png
  • nodeのバージョンを確認する
$ node -v
v12.14.1
  • npm(パッケージマネージャ)のバージョンを確認する
$ npm -v
6.13.4

以上です。

参考:
https://jsprimer.net/use-case/setup-local-env/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity WebGL なゲームをNode.JS + jsdom + headless-glで動かしたかった

えっ このネタ続くの。。?

前回( Unity WebGLで使われているシェーダを抜き出してARBアセンブリを眺める )はWebGLビルドのUnityゲームをトレースして、使われているシェーダ命令があんまり多くないことを確認した。

ブラウザ上の動作では同期APIの実装に制約がありちょっと手を入れづらいため、Node.jsで動かしたかった。別案としてNW.jsを使うというのもあったが、今回の手法でもWebブラウザ側のDOMを使わないといけないところは一応クリアしている。

結果と手法

結局成功したんだか失敗したんだかよくわからないところまでは来たと思う。

結果

  • Node.jsでもUnity WebGLは起動して描画コマンドも発行する
  • でも描画がまっくらなので、まだ描画が正常かどうかは確認できていない

JSdomでWebAssemblyを使ったサイトがそのまま動くのは地味にすごい気はする。

手法

Node.jsにはWebAssemblyがあり、Webプラットフォームの実装としては:

のようなものが既にある。これらはUnity WebGLビルドを動かすには十分に見える。なので、 JSdomで作成したNode.js上の仮想ブラウザ環境の windowdocument 各オブジェクトを都合よくpolyfillし、Unity WebGLが生成したWebページをそこにロードする が基本的な方針となる。

結果、実際に描画コマンドの発行は確認でき内容は正しそうだが(Error と出ているのは単にError.captureStackTrace でスタックトレースを拾っているからで、glErrorになるようなエラーが無いことは確認している)、

SnapCrab_NoName_2020-1-19_18-50-3_No-00.png

出画は真っ暗だった。

out22.png

描画内容自体はゼロ埋めではない(濃いグレーになっている)し、描画コマンドが出ているのは確認できているので、headless-gl側の問題だと考えている。

Unity WebGL ビルドの構造

(今回は Unity 2019.2.17f1 のWebGL 1.0ビルドを元に書いている。)

Unity WebGLビルドは、要するに Emscripten でビルドしたUnityエンジンを単に動作させているだけで、Webプラットフォームに移植されている部分は殆んどない。例外は通常のビルドではFMODを使用しているオーディオエンジンで、WebGLビルドでは自前のオーディオミキシングを.wasm側に持っているようだ。

重要な構成ファイルは4つある。これらのファイルを直接見るには、Unityのビルド設定で圧縮を事前に無効化しておく必要がある。

SnapCrab_NoName_2020-1-19_18-18-29_No-00.png

UnityLoader.js

Build/UnityLoader.js はJavaScript製の起動ルーチンで、こちらはUnity手製とみられる。通常のビルドではMinifyされるがDevelopment buildを選択することでMinify前のソースを見ることができる。

UnityLoaderでは:

  1. ゲーム本体のIndexeddbへのキャッシュ (HTTPキャッシュを使わないのは安全と圧縮のため?)
  2. ブラウザ検出とUser Agentベースでの非対応ブラウザの起動抑止
  3. Emscriptenの出力に Math.fround の省略パッチを当てる
  4. プログレスバーの出力
  5. Web Workerを使用した gzip、brotli のデコード
  6. アセットのEmscriptenファイルシステム側への投入
  7. 実際のゲーム本体のロード

といった作業をしている。

対応ブラウザチェックは、

"Please note that your browser is not currently supported for this Unity WebGL content. Press OK if you wish to continue anyway."

のようなメッセージを表示して起動を止めてしまう。ここは回避できなかったので今回の実験ではパッチしてしまった。

NAME.wasm.framework.unityweb

Build/<NAME>.wasm.framework.unityweb はEmscriptenが出力したJavaScript側のコードで、C++(IL2CPP)部分とDOMのインターフェースは基本的にここに集約されている。

... これはEmscripteのランタイムほぼそのままなので特にコメントはなし。ただ、JSdomで要素のwidthheightを変更する方法がわからなかったので、こちらに手を入れて無理矢理表示状態を作りだしている。

NAME.wasm.code.unityweb

Build/<NAME>.wasm.code.unityweb は、IL2CPPとEmscriptenによって生成されたエンジン本体およびゲームコードで、少くともDevelopment buildでは完全にシンボル入りになるため wasm2wat コマンド等で逆アセンブルできる。

例えば、jsdomで作ったElementは自動的に width = height = 0 となるが、この状況だと:

        call $__Z12InputProcessv
        block  ;; label = @3
          block  ;; label = @4
            call $__Z14GetPlayerPausev
            i32.const 2
            i32.eq
            br_if 0 (;@4;)
            i32.const 0
            call $_emscripten_is_webgl_context_lost
            br_if 0 (;@4;)
            call $_JS_SystemInfo_GetCurrentCanvasWidth
            i32.eqz
            br_if 0 (;@4;) ;; ★ ゼロだったらレンダリングループを中断
            call $_JS_SystemInfo_GetCurrentCanvasHeight
            i32.eqz
            br_if 0 (;@4;) ;; ★ ゼロだったらレンダリングループを中断

となっていて、何らかの方法でfakeしないといけないことが判る。今回は NAME.wasm.framework.unityweb の方をパッチした。

NAME.data.unityweb

Build/<NAME>.data.unityweb はゲームアセットを格納したアーカイブで、 UnityLoader.js でEmscriptenのファイルシステム側に投入される。

現時点ではメモリ上に完全なファイルシステムを構築している。つまり、WebGLビルドではアセットのサイズがそのままメモリ消費量になる。

DOM API のfake

JSdomやheadless-glでそれなりの量のAPIを実装できているが、いくつかは手で実装する必要があった。

createElement

JSdomは実は canvas 要素を実装しており、 2d コンテキストは使うことができる。 ...が、残念ながらWebGLコンテキストはサポートしていないため自前の実装で置き換える必要がある。

今回はロードしたスクリプトから呼ばれる createElement のみをhookして置き換える方向とした。

    function proxyCEl(nam){
        console.log("PROXY CEL", nam);
        if(nam == "canvas"){
            let cv = d.super_createElement("div"); // ★ 適当に div 要素でお茶を濁す
            cv.getContext = function(type, attr){ // ★ div要素に getContext メソッドを追加する
...

Emscripten側は実際に生成された要素が div であっても、 getContext メソッドさえ有れば正常に動作する。Duck typing。

getContext で得たコンテキストには WebGLDebugTools( https://github.com/KhronosGroup/WebGLDeveloperTools )を入れて呼び出しのトレースを実施している。ただ WebGLDebugTools はextensionを正常に処理できなかったため、extensionについてはhookしないように適当にパッチした。

createObjectURL 、 revokeObjectURL

createObjectURL は、ブラウザ内部のポインタを指すURL(Blob URL)を生成するもので、HTML5 File APIの一部になっていて( https://www.w3.org/TR/2019/WD-FileAPI-20190911/#creating-revoking )、ここで生成したURLがfetchで使えるようになることが期待される( https://www.w3.org/TR/2019/WD-FileAPI-20190911/#blob-url )。しかし、JSdomにはこの機能が無いため、自前で実装してやる必要がある。

revokeObjectURL は、Development buildではデバッグの都合で使用されていない。(ブラウザ側のDeveloper toolsで見るのに不便だからと考えられる)

今回は Promise を生成するクロージャの形で適当なところに保存し、 fetch 操作ではその Promise そのものを返す(2回目以降は Promise.resolve で直接値を返す)ことにした。 createObjectURL で生成されるURLの形式は決まっているが今回は適当に付けている。

    function createObjectURL(blob){
        let cache = false;
        function cb(){ // ★ このクロージャをBlob URLと関連付けて保存する
            if(cache){
                return Promise.resolve(cache); // ★ 2回目以降は cache を直接resolveする
            }else{
                return new Promise((res, rej) => {
                    const the_reader = new w.FileReader(); // ★ 初回はFile APIで読み出す
                    the_reader.onload = (e => {
                        const bv = the_reader.result;
                        cache = Buffer.from(bv);
                        res(cache);
                    });
                    // FIXME: ??? It seems JSDOM ArrayBuffer cannot move to
                    //        Buffer object. Use readAsText instead for now...
                    the_reader.readAsText(blob);
                });
            }
        }
        return blob_to_url(cb);
    }
function fetch_blob(uri){ // => promise
    const r = blobs[uri]();
    return r;
}

class MyLoader extends ResourceLoader { // ★ ResourceLoaderはJSdom本来のローダー
    fetch(url, options){ // (JSdomのfetchを実装している 、 HTML5のfetchではないことに注意)
        console.log("LOADER",url,options);
        if(url == "xblob:1"){ // ★ 1番のblobはローカルファイルに差し替え
            console.log("PATCH!!!");
            const buf = fs.readFileSync(path.resolve(__dirname, "patch1.js"));
            return Promise.resolve(buf);
        }else if(url.indexOf("xblob:") != -1){
            return fetch_blob(url); // ★ Promiseを生成して返す
        }else{
            return super.fetch(url, options); // ★ blob以外では本来のローダーを使う
        }
    }
}

Unityでは、この createObjectURL は、JavaScriptで展開される可能性のある .unityweb を保持するのに使用している。このため、MyLoaderクラスにはデバッグ用にファイルを差し替える機能も付けている。

Web Workers

UnityではHTTP経由で取得するファイルを解凍するためにWeb workerを使用している。実際の展開処理はプレイヤー設定で "Uncompressed" を選べばskipでき、そのためのコードは非常に単純なため、今回は真面目にWeb Workerを実装するのではなく単に関数だけ実行できるようにした。

Unityが供給するWeb workerコードは、 this.onmessage ハンドラでコマンドを受けとり、グローバルの postMessage 手続きでデータを返す簡単なものなので、

            const text = buf.toString();
            const src = "const obj = function (postMessage) {" + text + "}; obj";
            const ex = eval(src); // ★ 元のUnityのWorkerコードをwrapしたもの
            const home = this;
            const recvmsg = function(x,y){
                if(home.the_handler){
                    let ev = {data: x};
                    console.log("RECVMSG", x, y);
                    home.the_handler(ev);
                }else{
                    console.log("!! Q RECV MSG", x, y);
                    home.recvq.push([x,y]);
                }
            }
            this.workerobj = {};
            this.workerobj._init = ex; // ★ 元のコードが this にアクセスしているため、適当なオブジェクトに付ける
            this.workerobj._init(recvmsg);
            console.log("WORKER STANDUP", this.workerobj);

のように function(postMessage){ /* 元のUnityのWorkerコード */}eval してWorkerコードのグローバル変数 postMessage をエミュレートする形にした。

元のコードはJavaScript的な thisonmessage手続きを新設するため、wrapしたコードを呼び出す前に適当なオブジェクトに付け、後から

                this.workerobj.onmessage(ev);

のように使用(Workerへのメッセージングのエミュレート)している。

requestAnimationFrame

requestAnimationFrame は、通常のブラウザではVSYNC(物理的な画面の更新完了)のたびに呼ばれる。ただし、今回は面倒なのでフリーラン(全力で描画する)とした。つまり、単にNode.jsの process.nextTick にコールバックを登録して即呼び出すだけとしている。

    function sleep(ms){
        return new Promise((res) => setTimeout(res, ms));
    }

    function proxyRAF(cb){
        //console.log("RAF", cb);
        process.nextTick(async function(){
            //await sleep(100); // ★ 描画速度の調整用
            const now = w.performance.now(); // ★ JSdomの Window.performance を使用して現在時刻を返す
            console.log("RAF", now);
            cb(now);
            update_screenshot();
        });
        return 99.99; // ★ これは cancel 用だが使われないので適当な値を返す
    }

かんそう

... いきなりUnityをやらないで、Emscriptenで書いたコードで小さく試すべきだったね。。ただ、もう必要なWebAPIは揃えてしまったし面倒なところは結局Unity固有だったので何とも。。

次はheadless-glを自前のWebGL実装に置き換えてみる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Express.jsコードリーディング

Express.jsのコードを読んでみたのでまとめることにしました。
普段の業務ではNode.jsもExpressも利用していないので、JavaScriptの基本的なコーディングやライブラリの内容なども合わせてまとめておこうと思います。
間違っている内容がありましたら編集リクエストをいただけますと幸いです。

Expressコードリーディング

対象のコード

今回コードリーディングする対象のコードです。
ただHello Worldするだけのコードになります。

index.js
const express = require('express')
const app = express()

app.get('/', (req, res) => {
    res.send('Hello World');
})

app.listen(3000);

nodeでindex.jsを起動します。
起動後curlでレスポンスを確認します。

$ node index.js 
$ curl localhost:3000
Hello World

expressインスタンス生成

まずはconst app = express()の処理を追っていきます。

ここで最初に呼び出されるのは、expressライブラリのルートディレクトリ上に存在するindex.jsファイルです。

index.js
module.exports = require('./lib/express');

上記はmoduleの作成のみで、処理本体はlib/express.jsファイルです

lib/express.js
/**
 * Expose `createApplication()`.
 */

exports = module.exports = createApplication;

/**
 * Create an express application.
 *
 * @return {Function}
 * @api public
 */

function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  // expose the prototype that will get set on requests
  app.request = Object.create(req, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  // expose the prototype that will get set on responses
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  app.init();
  return app;
}

一つずつ処理を見ていきます。

アプリケーション作成

まずはこちらになります。

lib/express.js
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

appのfunctionを生成していますが、こちらがExpressの入り口となるアプリケーション部分です。
HTTPリクエストをされるとここが実行されます。

プレーンなNode.jsだけでここを表現すると以下のような処理になります。

サンプル
const http = require('http')

var app = function(req, res, next) {
  app.handle(req, res, next);
};
app.handle = function handle(req, res, callback) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}

http.createServer(app).listen(3000);

アプリケーションメソッドをコピーしてくる(継承する)

appはEventEmitterとprotoをコピーします。

lib/express.js
var EventEmitter = require('events').EventEmitter;
var mixin = require('merge-descriptors');
var proto = require('./application');
var req = require('./request');
var res = require('./response');

中略

  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  // expose the prototype that will get set on requests
  app.request = Object.create(req, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  // expose the prototype that will get set on responses
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

ここでコピーしているproto(application.js)がapplicationのメイン処理が入っている部分です。
具体的な処理は後々出てくるのでそこで説明します。

その他、EventEmitterライブラリやExpressプロジェクトのルートディレクトリにあるrequest.js、response.jsの内容もコピーしています。

アプリケーション初期設定

lib/express.js
  app.init();

ここで読みだしているのは、lib/application.js内に設定されているinitメソッドです。
この中で行なっているのは、アプリケーションをデフォルトの設定で設定しています。

例えば、HTTPヘッダーにx-powered-byを返却するなどはこの中で設定しています。

Routingの設定

次にRouting設定です。

app.get('/', (req, res) => {
    res.send('Hello World');
})

の処理です。
こちらで呼び出されるapp.getlib/application.jsファイルの以下処理になります。

lib/application.js
methods.forEach(function(method){
  app[method] = function(path){
    if (method === 'get' && arguments.length === 1) {
      // app.get(setting)
      return this.set(path);
    }

    this.lazyrouter();

    var route = this._router.route(path);
    route[method].apply(route, slice.call(arguments, 1));
    return this;
  };
});

HTTPメソッドに紐づくfunctionの作成

以下の処理でHTTPメソッド(GET,POST,PUT,PATCH,DELETEなど)と同一名称のメソッドをappクラスに作成します。

lib/application.js
var methods = require('methods');

methods.forEach(function(method){
  app[method] = function(path){
    // 中略
  };
});

Routerクラスの移譲

次はthis.lazyrouter();の部分です。
ここは以下の処理になります。

lib/application.js
var Router = require('./router');

app.lazyrouter = function lazyrouter() {
  if (!this._router) {
    this._router = new Router({
      caseSensitive: this.enabled('case sensitive routing'),
      strict: this.enabled('strict routing')
    });

    this._router.use(query(this.get('query parser fn')));
    this._router.use(middleware.init(this));
  }
};

appクラスの_routerプロパティにRouterクラスを委譲しています。
Routerクラスはlib/router/index.jsファイルを参照しています。

this._router.useはMiddlewareの設定です。

this._router.use(query(this.get('query parser fn')));の設定は、クエリストリングをObjectに変換してくれます。

this._router.use(middleware.init(this));はMiddlewareの初期設定を行います。
X-Powered-Byヘッダーを付与するのもこちらになります。

Routeクラスの取得とLayerクラスをスタックに追加

続いては以下の処理です。
var route = this._router.route(path);

こちらは先ほど作成した_routerのrouteメソッドを利用して、RouteクラスとLayerクラスを作成します。
実際の処理はこちらです。

lib/router/index.js
var Route = require('./route');
var Layer = require('./layer');

/**
 * Create a new Route for the given path.
 *
 * Each route contains a separate middleware stack and VERB handlers.
 *
 * See the Route api documentation for details on adding handlers
 * and middleware to routes.
 *
 * @param {String} path
 * @return {Route}
 * @public
 */

proto.route = function route(path) {
  var route = new Route(path);

  var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
  }, route.dispatch.bind(route));

  layer.route = route;

  this.stack.push(layer);
  return route;
};

RouteクラスとLayerクラスを生成します。
そしてLayerクラスのrouteプロパティにRouteクラスを移譲しています。
作成したLayerクラスはRouterクラスのstackに格納しています。

呼び出し元にはRouteクラスを返却しています。

RouteクラスにLayerクラスをセット

続いては以下の処理です。
route[method].apply(route, slice.call(arguments, 1));

こちらは先ほど作成したRouteクラスに新しいLayerクラス作成したうえでセットします。
実際の処理はこちらです。

lib/router/route.js
methods.forEach(function(method){
  Route.prototype[method] = function(){
    var handles = flatten(slice.call(arguments));

    for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];

      if (typeof handle !== 'function') {
        var type = toString.call(handle);
        var msg = 'Route.' + method + '() requires a callback function but got a ' + type
        throw new Error(msg);
      }

      debug('%s %o', method, this.path)

      var layer = Layer('/', {}, handle);
      layer.method = method;

      this.methods[method] = true;
      this.stack.push(layer);
    }

    return this;
  };
});

ここではLayerクラスを作成してかつ、指定したメソッドをLayerクラスにセットします。
そして作成したLayerクラスをRouteクラスのstackに追加しています。

httpのlisten開始

最後に以下の構文でhttpをport3000で開始します。
app.listen(3000);

実際の処理はこちらです。

lib/application.js
app.listen = function listen() {
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};

中身については、アプリケーション作成の部分で説明した内容と同じなので省略します。

リクエストを受け付けた時の挙動

最後にリクエストを受け付けた時の挙動についてです。
このような順番で処理されます。

app->app.handle->router.handle->Layer.handle->Route.dispatch->Layer.hanldle->Express利用者が設定したfunction

ここでRouter、Layer、Routeクラスの関係性について図にまとめてみました。

Routing.png

Expressで利用しているNode.jsの構文やライブラリについて

上記まででExpressの処理の流れをざっと確認してきましたが、実際にコードリーディングするにあたり、普段見慣れないNode.jsの構文やライブラリがいつかあると思います。
そこでそれぞれの構文がどういうものなのかをまとめます。

mixin

参考:merge-descriptors
まずはmixinからです。
Expressでは、以下のように利用しています。

lib/express.js
var app = function(req, res, next) {
    app.handle(req, res, next);
};

mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);

mixinがどういうものか見ていきます。

サンプル
var mixin = require('merge-descriptors');

var app = function() {
};
var application = function() {}
application.handle = function() {
        return 'Hello mixin';
}

mixin(app, application, false);

console.log(app.handle());  // Hello mixin

このようにappクラスにapplicationクラスのメソッドを継承させています。
これがmixin(merge-descriptors)の利用方法になります。

EventEmitter

参考:GitHub->node->Events

続いてはEventEmitterです。
EventEmitterを利用することでイベント駆動型の設計が可能になります。

サンプル
var mixin = require('merge-descriptors');
var EventEmitter = require('events').EventEmitter;

var app = function() {};

mixin(app, EventEmitter.prototype, false);
app.init = function() {
    app.on('mount', () => {
        console.log('emit!');
    });
    console.log('init')
}
app.send = function() {
    app.emit('mount');
}
app.init();  // init
app.send();  // emit!

上記のように、on.('イベント名')で事前に処理を受け付けるタイミングを作成してemit('イベント名')を実行する際に、onの中に指定したfunctionが実行されます。

Object.create

続いてはObject.createについてです。
Expressでは以下のように利用されています。

Expressでの利用
// expose the prototype that will get set on requests
  app.request = Object.create(req, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

こちらもmixinと同じようにメソッドを継承します。

サンプル
var app = function() {};
var req = function() {};
req.headers = function() {
    return 'req!';
}

app.request = Object.create(req);
console.log(app.request.headers()); //  req!

apply&call&bind

applyとcallとbindについてです。
Express.jsでは様々な場所でこのAPIが利用されています。

サンプル
route[method].apply(route, slice.call(arguments, 1));

var args = slice.call(arguments, 1);

こちらは様々な記事で解説されているため、詳細は割愛しますが、簡単に試して見ます。

サンプル
var app = function() {}
app.say = function(name, age) {
    return name + '' + age + '歳です。';
}

console.log(app.say('Taro', 16));  // Taroは16歳です。
console.log(app.say.apply(app, ['Taro', 16]));  // Taroは16歳です。
console.log(app.say.call(app, 'Taro', 16));  // Taroは16歳です。
var say = app.say.bind(app, 'Taro', 16);
console.log(say());

参考

Expressガイド

他にもExpressの解説をされている方がいらっしゃいましたので紹介します。
エンジニアの教養、フレームワーク解剖学【Express編】

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Promiseとasync/awaitを使った同期処理

Node.jsを使ったプログラムを書いていて、同期的に処理してほしいところを全て非同期でやっちゃう可愛いnodeのおかげでいくつか勉強になったことをまとめる。

同期処理を考える手順

1.まずはコールバック関数を使う

まず最初に思いつくのはこれ。コールバックを使えば、処理1の値を使った処理2みたいなことができる。
が、簡単な処理に関してはこれで十分だが、処理が複雑になってくるといわゆる「コールバック地獄」と言われる見た目も処理も地獄のようなプログラムができてしまう。要はネストが深くなって何がなんだかよくわからなくなる。

sample.js
function 処理2{
 callback(処理1)
}

2. Promiseを使う

そこで登場するのがPromise。こいつは結構優れもので、書き方を覚えてしまえばコールバック地獄を回避できる。
そもそもPromiseは何をしているかを簡単に説明すると、オブジェクト(引換券的なもの)を先に渡しておいて、resoleが返ってきた、つまり処理が終わればそのオブジェクトと結果を交換するよって感じ。
もっと砕いて言うと、電子レンジがPromiseで、人は何かを温めるとき電子レンジに食べ物を入れる。これが自分のものだよって言うことを示す引換券をもらうとして、温めが終わったら取りにいくイメージ。その間は何をしててもokだから要はPromise単体だと非同期処理を行なっている。

sample.js
var result_1 = new Promise(function(resolve) {

    resolve("result_1成功");

});

result_1.then(function(){
 //処理
}).then(fucntion(){
})
・・・

同期処理にしたければPromiseのthenを使う。これは次々に処理を記述しているが、結局処理が終わらない(resoleが返ってこない)と次に進まないので同期的な処理になる。thenを使えば、コールバックを綺麗に記述でき、メンテナンス性も向上する。

async/awaitを使った方がもっと綺麗

これに関してはいろんなサイトでごちゃごちゃ書いているが、要は使えたらいいので簡潔に言うと、asyncな関数で待ちたい処理をawaitにするだけ。

sample.js
// await式を使いたいので、async関数を宣言する
const main = async () => {
    // 処理1のPromiseを宣言する
    const routine1 = new Promise(resolve => {
      //略
      resolve('end')
    })
    const result1 = await routine1; // 処理1の完了を待ち結果を受け取る
    // 処理2以下を記述……
  }
}

// 関数を実行
main();

async/awaitが最も綺麗にかけるのでおすすめ。

注意事項 Promiseを変数に入れるか関数のreturnにするか

sample.js
const routine1 = new Promise(resolve => {
      //略
      resolve('end')
    })

function routine1(){
 return new Promise(resolve =>{
 //略
});
}

この違いは結構大事。変数のroutine1を呼び出す場合は、呼び出す前にこの代入式を記述するとその時点で処理が実行される。一方で関数の場合は、呼び出した時点で初めてPromiseの処理が始まるという違い。
また、ループでなんどもPromise処理を使いたい時には、functionで毎回呼び出さす必要がある。変数だと一回resolveを返してしまえばそのPromiseは御役御免となり一生使えない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ejsの<%= >と<%- >の違い

htmlの要素として出力したいか、文字として出力したいかで使い分ける
→基本的には<%= %>で、子要素の出力を一緒に使いたいときには<%- %>を使用す

そもそもEJSとは

ejsとはテンプレート機能を実現するNode.jsのパッケージです。

1,<%= >

ejsに用意されている独自機能です。
このタグは、指定した変数の値を出力するものです。

<%=変数 >

2,<%- >

ejsに用意されている独自機能です。
HTMLをエスケープ処理しないでそのまま出力する為のものです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

node.js製のlamdaアプリをコマンド一発でアップロードする方法

node.jsでlambdaのアプリを作っているのですが、コードやパッケージが増えてくるとインライン編集出来なくなったり、zipでアップロードする必要が出てきます。ちょっとした変更でもわざわざawsのサイトに行ってアップロードするのは面倒ですし、複数のlambdaアプリを作っていたら間違えて別の関数にアップロードしてしまった、といった事故も起きるかもしれません。そこで、コンソールからコマンドでアップロードできるようにする方法を書きます。

※動作確認環境: macOS 10.14.6

aws CLIの設定

まずawsのCLIをインストールします。バージョンが出ればOKです。

$ brew install python3
$ pip3 install aws
$ aws --version

次にawsのユーザー情報を設定する必要がありますが、アクセスキーが必要になります。IAMでAdministratorAccessポリシーを持ったユーザーを作成しておきます。
https://blog-asnpce.com/technology/313

アクセスキー、シークレットアクセスキー、リージョンをaws configureで設定します。

$ aws configure
AWS Access Key ID [None]: xxxxxxxxxxxxxxx
AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Default region name [None]: ap-northeast-1
Default output format [None]: // 設定せずそのままEnter

これでコンソールからawsのcliを使用するときに自分のアカウントを操作することができるようになりました。

package.jsonの編集

package.jsonを編集し、lambdaアプリのディレクトリでnpm run deployを実行するとアップロード出来るようにします。
package.jsonのscripts内にdeployというプロパティ名でコマンドをセットします。セットするコマンドは以下です。このコマンドを実行することにより、package.jsonが入っているディレクトリをzipに圧縮してからCLIでアップロード出来ます。
{}内はご自身のlambdaアプリに合わせてください。zipのファイル名はなんでもいいです。

zip -r {zipファイルの名前}.zip ./ -x \"*.zip\" && aws lambda update-function-code --function-name {lambda関数の名前} --zip-file fileb://{zipファイルの名前}.zip
package.json
{
  "name": "prodfunc",
  "version": "1.0.0",
  "engines": {
    "node": ">=12.0.0"
  },
  "description": "",
  "main": "index.js",
  "scripts": {
    // ここに追加
    "deploy": "zip -r prodfunc.zip ./ -x \"*.zip\" && aws lambda update-function-code --function-name prodfunc --zip-file fileb://prodfunc.zip"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    // 
  }
}

コンソールで以下を実行し、エラーが出なければ成功です。

$ npm run deploy

参考
https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

node.js製のlambdaアプリをコマンド一発でアップロードする方法

node.jsでlambdaのアプリを作っているのですが、コードやパッケージが増えてくるとインライン編集出来なくなったり、zipでアップロードする必要が出てきます。ちょっとした変更でもわざわざawsのサイトに行ってアップロードするのは面倒ですし、複数のlambdaアプリを作っていたら間違えて別の関数にアップロードしてしまった、といった事故も起きるかもしれません。そこで、コンソールからコマンドでアップロードできるようにする方法を書きます。

※動作確認環境: macOS 10.14.6

aws CLIの設定

まずawsのCLIをインストールします。バージョンが出ればOKです。

$ brew install python3
$ pip3 install aws
$ aws --version

次にawsのユーザー情報を設定する必要がありますが、アクセスキーが必要になります。IAMでAdministratorAccessポリシーを持ったユーザーを作成しておきます。
https://blog-asnpce.com/technology/313

アクセスキー、シークレットアクセスキー、リージョンをaws configureで設定します。

$ aws configure
AWS Access Key ID [None]: xxxxxxxxxxxxxxx
AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Default region name [None]: ap-northeast-1
Default output format [None]: // 設定せずそのままEnter

これでコンソールからawsのcliを使用するときに自分のアカウントを操作することができるようになりました。

package.jsonの編集

package.jsonを編集し、lambdaアプリのディレクトリでnpm run deployを実行するとアップロード出来るようにします。
package.jsonのscripts内にdeployというプロパティ名でコマンドをセットします。セットするコマンドは以下です。このコマンドを実行することにより、package.jsonが入っているディレクトリをzipに圧縮してからCLIでアップロード出来ます。
{}内はご自身のlambdaアプリに合わせてください。zipのファイル名はなんでもいいです。

zip -r {zipファイルの名前}.zip ./ -x \"*.zip\" && aws lambda update-function-code --function-name {lambda関数の名前} --zip-file fileb://{zipファイルの名前}.zip
package.json
{
  "name": "prodfunc",
  "version": "1.0.0",
  "engines": {
    "node": ">=12.0.0"
  },
  "description": "",
  "main": "index.js",
  "scripts": {
    // ここに追加
    "deploy": "zip -r prodfunc.zip ./ -x \"*.zip\" && aws lambda update-function-code --function-name prodfunc --zip-file fileb://prodfunc.zip"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    // 
  }
}

コンソールで以下を実行し、エラーが出なければ成功です。

$ npm run deploy

参考
https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む