LoginSignup
12
12

More than 5 years have passed since last update.

コピペで Promise の練習

Last updated at Posted at 2017-06-12

概要

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

値を決め打ちする

resolve と reject

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

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);
  });

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

result
then
Hello Promise

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

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 の場合です。

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();

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

result
try
Hello Promise

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

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();

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

result
catch
Hello Promise

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

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 をコンストラクター関数呼び出しを使ったコードに書き換えてみましょう。

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

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

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

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

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

Promise.try を使う

Promise.tryTC39 で提案されているメソッドです。Promise のコンストラクターで指定するコールバックの内容を簡略化することが目的です。次のコードで試すことができます。

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

Polyfill のコードは TC39 のリポジトリをご参照ください。Bluebird にも実装が存在します。

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

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

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

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

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

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

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

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

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 も一緒に公開されています。Bluebird でも実装されています。

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

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

node --harmony_promise_finally test.js

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

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

run();

キャンセル

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

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 です。

main.ts
import('./hello').then(hello => {
    console.log(hello.getGreeting());
});
hello.ts
export function getGreeting() {
    return "Hello World";
}

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

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

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

npm install -g ts-node

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

tsc --lib ES2015 main.ts

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

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

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

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

main();

実行を遅延させる

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

// 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 を使って一定時間のあいだ同期的に処理を停止させることができます。

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

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

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

// 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秒経過");
12
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
12