TypeScript
AzureFunctions

Azure Functions を TypeScript で書いてみる。

More than 1 year has passed since last update.

Mac で、Azure Functions をいじろうと思って、C# もかけるけど、まずは、node + Azure Functions CLI の組み合わせで、書いてみようと思った。簡単な、 CosmosDB に接続するサンプル。

噂の Callback hell

一番簡単そうな、CosmosDB の node からのチュートリアルをやってみた。

コードが見当たらなかったので、自分で書いたのを GitHub に上げておいた。

ちなみに、こんな感じのが3ファイルぐらい必要だった。単なる ToDo に、、、orz

taskDao.js

var DocumentDBClient = require('documentdb').DocumentClient;
var docdbUtils = require('./docdbUtils');

function TaskDao(documentDBClient, databaseId, collectionId) {
    this.client = documentDBClient;
    this.databaseId = databaseId;
    this.collectionId = collectionId;

    this.database = null;
    this.collection = null;
}

TaskDao.prototype = {
    init: function (callback) {
        var self = this;

        docdbUtils.getOrCreateDatabase(self.client, self.databaseId, function(err, db) {
            if (err) {
                callback(err);
            } else {
                self.database = db;
                docdbUtils.getOrCreateCollection(self.client, self.database._self, self.collectionId, function (err, coll) {
                    if (err) {
                        callback(err);
                    } else {
                        self.collection = coll;
                    }
                });
            }
        });
    },
    find: function (querySpec, callback) {
        var self = this;
        self.client.queryDocuments(self.collection._self, querySpec).toArray(function (err, results) {
            if (err) {
                callback(err);
            } else {
                callback(null, results);
            }
        });
    },

    addItem: function (item, callback) {
        var self = this;

        item.date = Date.now();
        item.completed = false;

        self.client.createDocument(self.collection._self, item, function (err, results) {
            if (err) {
                callback(err);
            } else {
                callback(null, results);
            }
        });
    },

    updateItem: function (itemId, callback) {
        var self = this;

        self.getItem(itemId, function (err, doc) {
            if (err) {
                callback(err);
            } else {
                doc.completed = true;
                self.client.replaceDocument(doc._self, doc, function(err, replaced) {
                    if (err) {
                        callback(err);
                    } else {
                        callback(null, replaced);
                    }
                });
            }
        });
    },

    getItem: function (itemId, callback) {
        var self = this;

        var querySpec = {
            query: 'SELECT * FROM root r WHERE r.id = @id',
            parameters: [{
                name: '@id',
                value: itemId
            }]
        };

        self.client.queryDocuments(self.collection._self, querySpec).toArray(function (err, results) {
            if (err) {
                callback(err);
            } else {
                callback(null, results[0])
            }
        });
    }
};

module.exports = TaskDao;

私の感想は、、、これが、コールバックヘルと言うやつか、、、こんなしょーもないことするのに、こんなコードが要るとは、、、辛すぎる。だった。そうだ、TypeScript だったらどうだろう。楽じゃないかな?

ライブラリを調べてみるとあった!

こ、これだ、これしかない! これを使って、Functions を書いてみよう。

Azure Functions を TypeScript 対応にしてみる

とりあえず、HttpTrigger を TypeScript に変えてみる。ちなみに、Azure Functions の TypeScript は、現在 Preview になって要るが、Azure Functions CLI の、2.0はまだ対応していない。自力だ。

Function App を作成

$ func init
Writing .gitignore
Writing host.json
Writing local.settings.json
Created launch.json

Initialized empty Git repository in /Users/ushio/Codes/AzureFunctions/some/.git/

Functions を作成

$ func new
Select a language: 
1. C#
2. JavaScript
Choose option: 2
JavaScript
Select a template: 
1. BlobTrigger
2. HttpTrigger
3. QueueTrigger
4. TimerTrigger
Choose option: 2
HttpTrigger
Function name: [HttpTriggerJS] 
Writing /Users/ushio/Codes/AzureFunctions/some/HttpTriggerJS/index.js
Writing /Users/ushio/Codes/AzureFunctions/some/HttpTriggerJS/sample.dat
Writing /Users/ushio/Codes/AzureFunctions/some/HttpTriggerJS/function.json

テンプレートが生成された

TypeScript 環境を作る

cd HttpTriggerJS
npm init
tsc --init
cp index.js index.ts

index.ts を次のように変える

index.ts

export async function run (context: any, req: any) {

typeconfig.json を次のように変える。これをしないと、async メソッドは、promise を返さないとエラーになる。ところが、最初の1つ目のfunction では返しても仕方がないケースがあるので、新しいバージョンの TypeScript ではエラーにならなくなっている。

{
  "compilerOptions": {
    /* Basic Options */                       
    "target": "ES2015",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd' or 'es2015'. */
    "lib": [ "es2015"],   

can't find process error

もしかすると、こんなエラーに出会うかもしれない。

index.ts(7,34): error TS2304: Cannot find name 'process'.
index.ts(7,65): error TS2304: Cannot find name 'process'.

このケースは、これで解消する。そもそも process 見つからんとかおかしいよね。これはバグ。

npm install --save-dev @types/node

下記の議論をみてみると詳細が書いてありJます。

下記のエラーは先ほどのtypeconfig.json の設定で解消されます。

error TS2705: An async function or method in ES5/ES3 requires the 'Promise' constructor.  Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your `--lib` option.

ついに Azure Functions

ここまで設定すると、無事、CosmosDB にアクセスするファンクションが動きました。簡単!

import * as DB from "documentdb-typescript";


export async function run (context: any, req: any) {
    context.log('JavaScript HTTP trigger function processed a request.');

    const client = new DB.Client(process.env["COSMOS_DB_HOST"], process.env["COSMOS_DB_KEY"]);
    client.enableConsoleLog = true;

    await client.openAsync();
    console.log(await client.getAccountInfoAsync());
    var dbs = await client.listDatabasesAsync();

    if (req.query.name || (req.body && req.body.name)) {
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: "Hello! " + (req.query.name || req.body.name) + dbs.map(db => db.id)
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body"
        };
    }
    context.done();
};

ちなみに、上記のは、documentdb-typescript のサンプルそのままだが、最初は、サンプルのままだと、エラーだった。Issue を書いて、解決したので、プルリクエストを書いておいた。

TypeError: Cannot read property 'getDatabaseAccount' of undefined

次のコミットが参考になります。 commit.

ToDo

残念ながら、TypeScript のローカルデバッグはできません。(生成されたjsはできると思うけど)。どうやったらできるか考えてみます。

Resource

実は、このサンプルを GitHub においています。