Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

メソッドチェーンのパターンを考えた

More than 1 year has passed since last update.

メソッドチェーンのパターンを考えた

おそらく、私はただpipeline operatorが欲しいだけだろう。以下のようなコードを本当に嫌っている。

const x1 = f(x0);
const x2 = g(x1);
const x3 = h(x2);

Promiseの本懐はこれを解決することではないが、メソッドチェーンでpipeline operatorのようなことができるので、好きだ。もともとRubyのObject#tapも好きだったし。

await Promise.resolve(x0).then((x) => {
  return Promise.resolve(f(x));
}).then((x) => {
  return Promise.resolve(g(x));
}).then((x) => {
  return Promise.resolve(h(x));
});

シンタックスはいかついが、変数がクロージャに閉じていて気持ちがいい。こういうものを欲しいと思った時にサクッと対応したいけど、毎回考えてしまうので、今回は一般的に考えた。

RubyのObject#tapの再実装

Object.prototype.tapにブチ込めばよい。

Object.prototype.tap = function (callback) {
  return callback(this);
};

簡単すぎるし、モンキーパッチなので、これを避けていきたい。

基礎パターン (Promise風)

function createThenable(object, callback) {
  const _object = callback(object);
  return {
    value: _object,

    then: function (_callback) {
      return createThenable(_object, _callback);
    },

    await: function () {
      return _object;
    },
  };
}

awaitは、操作感が伝わりやすいかと思い、この命名にした。valueをプロパティで呼ぶだけでもよい。上のコードは、このように使う。

createThenable(1, (i) => {
  return i + 1;
}).then((i) => {
  return i + 1;
}).then((i) => {
  return i + 1;
}).await();

Object#tapが好きだと書いたが、これを再実装するには、createThenableをcreateTappableに、thenをtapに書き換えればそれで済むだろう。よく考えれば、前述したPromiseの副産物はObject#tapと同じものだった。

Array.prototype.pushでメソッドチェーンしたい

そういえば、RubyのArray#pushは返り値がselfなのでメソッドチェーンできるけど、JavaScriptのArrayはそうなっていない。RubyからJavaScriptへ乗り換えた者には覚えがあるだろう。

createThenable([], (array) => {
  array.push(1);
  return array;
}).then((array) => {
  array.push(2);
  return array;
}).then((array) => {
  array.push(3);
  return array;
}).await();

さきほどのコードをそのまま利用するとこうなるが、明らかに冗長なので特化させたい。

function pushArray(array, callback) {
  array.push(callback(array));
  return {
    push: function(_callback) {
      return pushArray(array, _callback);
    },

    array: array,
  };
}

すると、以下のように書くことができる。

pushArray([], () => {
  return 1;
}).push(() => {
  return 2;
}).push(() => {
  return 3;
}).array;

手軽なルーターを書きたい

ルーターというのは、Web Application Frameworkのルーターのことで、正規表現にマッチしたら引数のクロージャを実行する、というのを繰り返すものが欲しい。RegExp.prototype.execの返り値がtruthyの場合に、クロージャに引数として渡したいし、それをマッチするまで繰り返したい。この記事を書くきっかけとなった問題意識がこれ。

function router(routes, regexp, handler) {
  routes.push({ regexp: regexp, handler: handler });
  return {
    bind: function (_regexp, _handler) {
      return router(routes, _regexp, _handler);
    },

    route: function (string) {
      for (let i = 0; i < routes.length; ++i) {
        const r = routes[i];
        const match = r.regexp.exec(string);

        if (match) {
          return r.handler(match);
        }
      }
    },
  };
}

この様に使う。

router([], /aaa/, (match) => {
  return console.log(1);
}).bind(/bbb/, (match) => {
  return console.log(2);
}).route('bbbbbb');

先頭の空配列リテラルが美しくないが、再帰のために必要なものなので、どうしても隠したければこれを隠蔽する関数を書くとよいだろう。ところで、私がルーターと呼んだ「正規表現にマッチしたらクロージャを実行し、マッチしなければ次の候補を試す」ものは頻出だと思うが、通常はどのように書くのだろうか。

nnn-school
IT×グローバル社会を生き抜く“創造力”を身につけ、世界で活躍する人材を育成する。
https://nnn.ed.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away