コピペで Promise の練習


## 概要

コピペで結果を確認しながら学べる Promise の学習教材を書きました。結果のふるまいを予測しやすいようにコードを短くし、ネットワークに依存しないようにしました。ES2015 のアロー関数や ES2017 の async/await にも対応した書き方を示しています。

## 値を決め打ちする

### resolve と reject
問題をかんたんにするために成功もしくは失敗の値を決め打ちにしましょう。最初は `Promise.resolve` で成功の場合を試してみましょう。

```js:test.js
const p = Promise.resolve("Hello Promise");

p.then(value => {
    console.log("then");
    console.log(value);
  })
 .catch(value => {
    console.log("catch");
    console.log(value);
  });
```

結果は次のようになります。

```text:result
then
Hello Promise
```

次は `Promise.reject` で失敗の場合を扱ってみましょう。

```js:test.js
const p = Promise.reject("Hello Promise");

p.then(value => {
    console.log("then");
    console.log(value);
  })
 .catch(value => {
    console.log("catch");
    console.log(value);
  });
```

### async/await 対応に書き換える

今度は async/await 対応に書き換えてみましょう。まずは `Promise.resolve` の場合です。

```js:test.js
async function main() {
  try {
    const text = await Promise.resolve("Hello Promise");
    console.log("try");
    console.log(text);
  } catch(err) {
    console.log("catch");
    console.log(err);
  }
}

main();
```

結果は次のようになります。

```text:result
try
Hello Promise
```

今度は `Promise.reject` に取り組んでみましょう。

```js:test.js
async function main() {
  try {
    const text = await Promise.reject("Hello Promise");
    console.log("try");
    console.log(text);
  } catch(err) {
    console.log("catch");
    console.log(err);
  }
}

main();
```

結果は次のようになります。

```text:result
catch
Hello Promise
```

関数の宣言による名前空間の汚染を減らすために関数の即時実行を使うこともできます。

```js:test.js
(async () => {
  try {
    const text = await Promise.resolve("Hello Promise");
    console.log("try");
    console.log(text);
  } catch(err) {
    console.log("catch");
    console.log(err);
  }
})();
```

### コンストラクター関数呼び出しへの書き換え

`Promise.resolve` をコンストラクター関数呼び出しを使ったコードに書き換えてみましょう。

```js:test.js
const p = Promise.resolve("Hello Promise");

const p2 = new Promise((resolve, reject) => {
  return resolve("Hello Promise");
});
```

次に `Promise.reject` を使うコードを書き換えてみましょう。

```js:test.js
const p = Promise.reject("Hello Promise");

const p2 = new Promise((resolve, reject) => {
  return reject("Hello Promise");
});
```

### Promise.try を使う

`Promise.try` は [TC39](https://github.com/tc39/proposal-promise-try) で提案されているメソッドです。Promise のコンストラクターで指定するコールバックの内容を簡略化することが目的です。次のコードで試すことができます。

```js
// https://twitter.com/RReverser/status/695678489937186816
Promise.try = function(cb) { return new this(r => r(cb())) };
```

Polyfill のコードは TC39 の[リポジトリ](https://github.com/tc39/proposal-promise-try/blob/master/polyfill.js)をご参照ください。Bluebird にも実装が存在します。

`Promise.resolve` は次のように書き換えることができます。

```js
const p = Promise.resolve('Hello Promise');

const p2 = Promise.try(() => {
  return "Hello Promise";
});
```

`Promise.reject` は次のように書き換えることができます。

```js
const p = Promise.reject(new Error('Error'));

const p2 = Promise.try(() => {
  throw new Error("Error");
});
```

## 実行のたびに結果が変わるようにする

今度はコンストラクター関数を使ったやり方で実行のたびに結果が変わるようにしてみましょう。時刻の秒数が偶数か奇数を判定する関数を使ってみます。

```js:test.js
function isEven() {
  let seconds = (new Date().getSeconds());
  return  seconds % 2 === 0;
}

const p = new Promise((resolve, reject) => {
  const text = "Hello Promise";
  return isEven() ? resolve(text) : reject(text);
});

p.then(value => {
    console.log("then");
    console.log(value);
  })
 .catch(value => {
    console.log("catch");
    console.log(value);
  });
```

## Promise.prototype.finally

TC39 で `Promise.prototype.finally` が提案されています。Node.js で試験的に実装されている (`--harmony_promise_finally `) ほかに TC39 のリポジトリで [Polyfill](https://github.com/tc39/proposal-promise-finally/blob/master/polyfill.js) も一緒に公開されています。Bluebird でも実装されています。

```js
const p = Promise.resolve("Hello")
  .then(value => console.log("then", value))
  .finally(() => console.log("finally"));
```

Node.js で実行するには次のようにフラグを指定します。

```bash
node --harmony_promise_finally test.js
```

async/await を使って書き換えると次のようになります。

```js
async function run() {
  try {
    const msg = await Promise.resolve("Hello");
    console.log("then", msg);
  } finally {
    console.log("finally")
  }
}

run();
```

## キャンセル

ユーザーの入力をもとに Promise の実行をキャンセルさせたいことがあります。ES2016 で対応された Promise にない機能なので、Bluebird のようなライブラリを導入する必要があります。

```js:test.js
const Bluebird = require('bluebird');
const p = Bluebird.resolve('成功');

p
.then(value => {
  console.log("then");
  if (!p.isCancelled()) {
    console.log("then でキャンセルを検出しました。");
  }
})
.catch(()  => {
  console.log('catch');
  if (!p.isCancelled()) {
    console.log("catch でキャンセルを検出しました。");
  }
})
.finally(()  => {
  console.log('finally');
  if (!p.isCancelled()) {
    console.log("finally でキャンセルを検出しました。");
  }
});

p.cancel();
```

## モジュールの動的なインポート

モジュールを動的にインポートできるようにする機能が TC39 (dynamic) で提案されています。2017年6月時点でステージ3の段階にあり、Webpack 2 および TypeScript 2.4 でサポートされます。Node.js には `--harmony-dynamic-import` フラグがありますが、2017年6月時点ではエラーになりました。

今回は TypeScript で試してみましょう。`import` 文の戻り値は Promise です。

```ts:main.ts
import('./hello').then(hello => {
    console.log(hello.getGreeting());
});
```

```ts:hello.ts
export function getGreeting() {
    return "Hello World";
}
```

記事を書いた時点では TypeScript 2.4 RC が公開されていたので、次のコマンドでインストールしました。

```bash
npm install -g typescript@rc
npm install @types/node
```

コンパイルと実行を同時に行うために ts-node を導入します。

```bash
npm install -g ts-node
```

`tsc` でコンパイルする際には `--lib` オプションを指定します。

```bash
tsc --lib ES2015 main.ts
```

`ts-node` を使う場合、`--compilerOptions` でコンパイルオプションを指定します。

```bash
ts-node --compilerOptions '{"target":"es2015"}' main.ts
```

async/await を使って書き直してみましょう。

```ts:main.ts
async function main() {
    const {getGreeting} = await import('./hello');
    console.log(getGreeting());
}

main();
```

## 実行を遅延させる

`setTimeout` を使って実行を遅延させる関数を定義してみましょう。`then` で指定できるように Promise を返すことにします。後からいろいろなメソッドチェーンをつけ加えることができます。Bluebird や RxJS は delay をあらかじめ定義しています。

```js
// https://gist.github.com/joepie91/2664c85a744e6bd0629c
function delay(time) {
  return (result) => {
    return new Promise(resolve => setTimeout(() => resolve(result), time))
  };
}

Promise.resolve("Hello")
  .then(delay(2000))
  .then(result => console.log(result));
```

## async/await で同期的に処理を停止させる (sleep)

async/await を使って一定時間のあいだ同期的に処理を停止させることができます。

```js
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

(async () => {
  console.log("スタート");
  await sleep(1000);
  console.log("1秒経過")
})();
```


async/await を使わないのであれば、`new Date().getTime()` を使って時刻を確認するやり方があります。

```js
// https://stackoverflow.com/a/17532524/531320
function sleep(ms) {
  let start = new Date().getTime(), expire = start + ms;
  while (new Date().getTime() < expire) { }
  return;
}

console.log("開始");
sleep(1000);
console.log("1秒経過");
```