かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Promise 対応していないコールバック形式のライブラリーを Promise にしたい

Node.js v8.1 で util.promisify っていう関数が追加されてたんですね。 古き良き伝統にしたがったコールバック形式の関数を Promise を返す形にしてくれる。つまり await 出来るようになる!

nodejs.org

ということで、今時コールバック形式のライブラリなんて…と思ってたら azure-storage がそれでした。 ということで使ってみよう。

使い方は簡単。

const f = util.promisify(hogehoge);

のようにコールバック形式の関数を渡してやると Promise を返す関数になるので…

await f();

とすれば OK。

じゃぁこんな感じに使えるかな。

import express from 'express';
import util from 'util';
import azure from 'azure-storage';

interface EntityType {
    PartitionKey: azure.TableService.EntityProperty<string>;
    RowKey: azure.TableService.EntityProperty<string>;
    Value: azure.TableService.EntityProperty<string>;
}

// Azure ストレージエミュレーターにつなぐ
const client = azure.createTableService('AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;');
async function getAll() {
     await util.promisify(client.createTableIfNotExists).bind(client)('test');
     const query = new azure.TableQuery().where('PartitionKey eq ?', 'a').select('Value');
     // 本当は util.promisify(client.queryEntities<EntityType>) みたいにしたかった。この場合どうするのが正解なんだろう
     return await util.promisify(client.queryEntities).bind(client)('test', query, null as any) as 
        azure.TableService.QueryEntitiesResult<EntityType>;
}

const app = express();
app.get('/', async (req, res) => {
    const result = await getAll();
    res.json(result.entries.map(x => x.Value._));
});

app.listen(3000);

注意点は、関数内部の this をちゃんと bind で指定してあげることくらいかな?

適当にデータつっこんだテーブルを用意して実行して localhost:3000 にアクセスすると…

f:id:okazuki:20190911202826p:plain

返ってきた!!

実際使うとしたら都度都度 util.promisify するのではなく、都合のいい感じのラッパークラスを用意する感じかなぁ?

import express from 'express';
import util from 'util';
import azure from 'azure-storage';

interface EntityType {
    PartitionKey: azure.TableService.EntityProperty<string>;
    RowKey: azure.TableService.EntityProperty<string>;
    Value: azure.TableService.EntityProperty<string>;
}

// こういうラッパークラスにめんどくさい処理は押し込んでおいて…
class TableService {
    constructor(private tableService: azure.TableService) {
    }

    public createTableService = util.promisify(this.tableService.createTableIfNotExists).bind(this.tableService);

    private _queryEntities = util.promisify(this.tableService.queryEntities).bind(this.tableService);
    public async queryEntities<T>(tableName: string, query: azure.TableQuery, token: azure.TableService.TableContinuationToken) {
        return await this._queryEntities(tableName, query, token) as
            azure.TableService.QueryEntitiesResult<T>;
    }
}

const innerClient = azure.createTableService('AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;');
const client = new TableService(innerClient);

async function getAll() {
    // 使う側は意識しなくてよくする
    await client.createTableService('test');
    const query = new azure.TableQuery().where('PartitionKey eq ?', 'a').select('Value');
    return await client.queryEntities<EntityType>('text', query, null as any);
}

const app = express();
app.get('/', async (req, res) => {
    const result = await getAll();
    res.json(result.entries.map(x => x.Value._));
});

app.listen(3000);

まとめ

ライブラリー側で await 出来るように Promise 返してくれるようにしてほしさある。