【JavaScript】第2引数でコールバックする関数をどうにかしたい


とりあげる課題とその問題点

今回取り上げるものは以下のようなコード
実例はkuromoji.js

(A: T, B: VoidFunction) => void
kuromoji/example/load-node.js
"use strict";

var kuromoji = require("../src/kuromoji");
var DIC_DIR = "dict/";

// Load dictionaries from file, and prepare tokenizer
kuromoji.builder({ dicPath: DIC_DIR }).build(function (error, tokenizer) { // ここが問題のコールバック
    var path = tokenizer.tokenize("すもももももももものうち");
    console.log(path);
    module.exports = tokenizer;
});

// ここでtokenizerは使えない

このtokenizerはこのままではコールバック関数外では利用できない。
これはスコープの関係上当たり前だが、少々使いづらい。

しかもコールバック地獄の原因なので使いたくない

というわけでこれを

test
const kuromoji = await tokenizer();
const res = kuromoji
      .tokenize('すもももももももものうち')
      .map(t => t.surface_form);

expect(res).toEqual(['すもも', '', 'もも', '', 'もも', '', 'うち']);

このようにfunctionalに書きたい。

解決策

new promiseする

typescript
import kuromoji, { Tokenizer, IpadicFeatures, TokenizerBuilder } from 'kuromoji';

const builder: TokenizerBuilder<IpadicFeatures> = kuromoji.builder({
  dicPath: 'node_modules/kuromoji/dict',
});

export const tokenizer = () => new Promise<Tokenizer<IpadicFeatures>>(done => {
  builder.build((_err, tokenizer) => {
    done(tokenizer);
  });
});

こうすると

test
const kuromoji = await tokenizer();
const res = kuromoji
      .tokenize('すもももももももものうち')
      .map(t => t.surface_form);

expect(res).toEqual(['すもも', '', 'もも', '', 'もも', '', 'うち']);

まとめ

取り上げたコードの名称を「継続」っていうらしいです。
async/await世代なので少し詰まった。

参考文献