俺、サービス売って家買うんだ

Swift, Kotlin, Vue.js, 統計, GCP / このペースで作ってればいつか2-3億で売れるのがポっと出来るんじゃなかろうか

【今更ES2015を追ってみるシリーズ 1】Promise編

f:id:hazumu1986:20151009141054j:plain

不定期連載です。

なんやかんやでしばらくRubyを書くことが多くて、本職であるJavaScriptに関するキャッチアップが滞ってたのでECMAScript 2015周りについて遅ればせながら追いかけて見ようと思います。

とりあえず某誰かのTweetで今後の「非同期処理はPromise一択みたいやな〜」みたないなのを見たのでこの辺から追ってみよう。

Promiseとは?

非同期処理をコールバック以外で実現する方法です。
jQueryのdeferred機構とほぼ同じですね。

api.jquery.com

サンプル

const funcAsync = function() {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {
      // 非同期処理成功の時
      resolve('async');

      // 非同期処理失敗のとき
      // reject('async');
    }, 1000);
  });
};

funcAsync() 
  // 非同期処理成功の時
  .then(data => {
    // 引数のdataにresolveで渡した値が入ってくる
    console.log('ok', data);
  })
  // 非同期処理失敗の時
  .catch(data => {
    console.log('ng', data);
  });

/*
thenで成功・失敗ともに拾う書き方
funcAsync() 
  .then(
    data => {
      console.log('ok', data);
    }, 
    data => {
      console.log('ng', data);
    });
*/

rejectの呼ばれ方

意図的にrejectをコールしなくてもPromiseの中でエラがー起こればrejectが呼ばれる。

const funcAsync = function() {
  return new Promise(function(resolve, reject) {
    throw TypeError("Something eorror is occured!");
  });
};

funcAsync() 
  .then(() => {
    console.log('ok');
  })
  .catch(() =>  {
    console.log('ng'); // ng
  });

複数の非同期処理のハンドリング

すべての処理を待つ
複数の非同期処理が全て終わった時に特定の処理をさせたい場合です。
例えばAPIを二本叩いた後でレンダリングさせるとか。※APIの作りも悪い気がするけど。

const funcAsync1 = function() {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {
      resolve('async1');
    }, 2000);
  });
};

const funcAsync2 = function() {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {
      resolve('async2');
    }, 1000);
  });
};

Promise.all([funcAsync1(), funcAsync2()]) 
  .then(data => {
    console.log('ok', data); // ok, ['async1', 'async2']
  });

一方が早く終わったら
funcAsync1とfuncAsync2でいずれかが先に終わったらthen以降を実行する。

Promise.race([funcAsync1(), funcAsync2()]) 
  .then(data => {
    console.log('ok', data); // ok, , 'async2'
  });

シーケンスを作る
アニメーションが一つ終わったら、次のアニメーションへ、そして最後にシーンを移動する。みたいな感じで一連のシーケンスを作りたい場合は以下の用に書けばいい感じですね。

  var i = 0;

  const funcAsync = function() {
    return new Promise(function(resolve, reject) {
      setTimeout(() => {
        i++;
        resolve(i);
      }, 2000);
    });
  };

  funcAsync()
    .then(data => {
      console.log(data); // 1

      return new Promise(function(resolve, reject) {
        setTimeout(() => {
          i++;
          resolve(i);
        }, 1000);
      });
    })
    .then(data => {
      console.log(data); // 2

      return new Promise(function(resolve, reject) {
        setTimeout(() => {
          resolve();
        }, 3000);
      });
    })
    .then(data => {
      console.log('終わり'); // 終わり
    });

こういう書き方はNG

jQueryのdeferred機構を使っていた時は、以下のような書き方をしていて、Promiseに渡す関数が長くなる時などにすっきり書けるので気に入っていたのですが、Promiseではできなそう。 誰か方法知ってたら教えてほしいです。

  const funcAsync = function() {
    var pr = new Promise();

    setTimeout(() => {
      pr.resolve();
    }, 1000);

    return pr;
  };

  funcAsync() 
    .then(() => {
      console.log('ok');
    });

エラー

Uncaught TypeError: Promise resolver undefined is not a function(…)

ES7では?

将来的にはasync/awaitが使えるようになるので、よりすっきり書けますね。 (ここまでやるとどれが非同期処理なのか頭を切り替えるのが大変そうですが。。)

const funcAsync1 = async function() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('async1');
      resolve();
    }, 2000);
  })
};

const funcAsync2 = async function() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('async2');
      resolve();
    }, 3000);
  })
};

(async function() {
  await funcAsync1();
  await funcAsync2();
  console.log("complete!");
}());

このサンプルはBabel公式サイトのrepleで動きますのでお試しあれ。

まとめ

Can I useを見た感じ割りと近い将来生でも使えるようになりそうですね。transpilerが入っているなら、他のライブラリを使うのをやめてすぐにでも使いたい限りです。 ⊂( ・∀・) 彡 =͟͟͞͞✹ オラッオラッ
Can I use... Support tables for HTML5, CSS3, etc

参考サイト・書籍