generatorとpromiseで行う非同期処理

2年ぐらい前にgeneratorでコールバック地獄から解放されるぜウェーイと話題になりましたが、今現在、世間ではgeneratorによる非同期処理ってどのくらい使われているんでしょうね? 自分たちのチームでは今更過去のコードを書き直すのも面倒なので使ってません。個人的にはasync/awaitがES.nextに入りそうなので今から使わなくてもいいかなぁ、という感じ。

generatorを使った書き方、よく忘れるのでここにメモしておく

まず適当なプロミスを返す関数を定義

'use strict';

// 引数を2倍して解決するpromiseを返す
function twice(value) {
  return Promise.resolve(value * 2);
}

// 引数ミリ秒後に解決するpromiseを返す
function sleep(time) {
  return new Promise((resolve) => {
    setTimeout(() => {resolve(time)}, time);
  });
}

// 失敗するpromiseを返す
function error(e) {
  return Promise.reject(new Error(e));
}

promiseをyieldして非同期処理を実行するexecAsyncを定義、おそらくcoモジュールもこんな感じの処理をしているんでしょうね。

// promiseをyieldして非同期処理を実行するexecAsyncを定義
function execAsync(generator) {
  const g = generator();

  let n = g.next();   // ここで最初のyieldまで実行される

  if (!n.done) {         // yieldが無かった場合はn.doneはtrueになる
    loop(n.value);     // n.valueは最初のyield式のpromise
  }

  function loop(p) {
    p.then((result) => {
      let n = g.next(result);   // nextの引数はyield式の評価結果として返される。そして次のyieldまで実行
      if (!n.done) {
        loop(n.value);
      }
    }).catch((e) => {
      let n = g.throw(e);
      if (!n.done) {
        loop(n.value);
      }
    });
  }
}

実行する

// 実行!
execAsync(function *() {
  let a;
  a = yield twice(1);
  console.log(a);   // => 2

  a = yield twice(2);
  console.log(a);   // => 4

  const t = yield sleep(1000);
  console.log(`${t}ミリ秒停止した`);

  try {
    yield error('Promiseがrejectされると');
  } catch (e) {
    console.log(`${e.message}エラーがスローされる`);
  }

  a = yield twice(3);
  console.log(a);    // => 6

  a = yield twice(4);
  console.log(a);    // => 8
});

結果

2
4
1000ミリ秒停止した
Promiseがrejectされるとエラーがスローされる
6
8

obj && obj.hoge && obj.hoge.fuga ... を少し簡単に書く

Qiitaにも投稿してみた


JSerの皆様につきましては、JSON.parseしたデータなど、多段ネストしたオブジェクトのプロパティにアクセスするとき、

const obj = {hoge: {fuga: {piyo: 1}}};

const value = obj && obj.hoge && obj.hoge.fuga && obj.hoge.fuga.piyo || 0;

のようなコードを書いてウンザリしてることと思われます。どうやって回避してますか?

回避策1

const value = propOf(obj, 'hoge', 'fuga', 'piyo') || 0;
function propOf(obj, var_args) {
  let result = obj;
  const args = Array.prototype.slice.call(arguments, 1);

  while (args.length > 0) {
    const p = args.shift();
    result = result[p];

    if (result === null || result === undefined) {
      return null;
    }
  }
    
  return result;
}

回避策2

アロー関数が使える今なら、こういう書き方でもいいんじゃないかと思い始めた。

const value = safe(() => obj.hoge.fuga.piyo) || 0;
function safe(f) {
  try {
    return f();
  } catch (e) {
    return null;
  }
}

俺ならこう書く、というアイディアがあれば教えてください。