JavaScript の Promise について理解しよう!

English follows Japanese. 英語の文章は後半にあります。

こんにちは。台湾在住、ShareWisのソフトウェアエンジニアのサイです。

この記事ではJavaScriptのPromiseオブジェクトについて紹介したいと思います。

なんでJavaScriptにPromiseが必要になったか?

まずもってPromiseはなぜ必要なのでしょうか?
その理由はJavaScriptに以下の2つの特徴があるためです。

  • シングルスレッド
  • 同期

上記の2つの特徴から、JavaScriptは基本的に1つのことを1つずつしか実行できません。そのため、何かが実行されているのを待たなければいけない状況が発生し、UXが悪くなってしまうケースがあります。

この問題を解決するために、ECMAScript 2015(いわゆるES6)ではJavaScriptにPromiseオブジェクトが導入されました。

JavaScriptのPromiseオブジェクトって何?

Promiseは非同期のコードの実行を行うオブジェクトです。

Promise.then()
は2つの引数を持ちます。resolve と reject です。一方は処理が成功したとき、もう一方は失敗したときのためのものです。
resolve(…) は非同期的に成功したときに呼び出され、reject(…) は非同期的に失敗したときに呼び出されます。

これらはオプショナルなので、成功したときだけ、失敗したときだけ、のどちらか一方だけを記述するのもOKです。

let doSomething = new Promise(function(resolve, reject) {
  // ファイルアップロードなど時間がかかる処理
  resolve(); // when successful
  reject();  // when error
});

doSomething.then(
  function(value) { /* code if successful */ },
  function(error) { /* code if some error */ }
);

Promiseオブジェクトのプロパティ

Promiseオブジェクトは3つのステートを持っています。

  • Pending: 最初の状態。以下の2つのどちらでもない状態
  • Fulfilled: 処理が完了して成功した状態
  • Rejected: 処理が失敗した状態

デフォルトのステートは pending です。resolve() が呼び出されると、fulfilledにステートが変わります。また、reject()が呼び出されるとrejectedになります。
Promiseの結果のみがステートに影響を与えることに注意してください。ステートを自身で変更することはできません。ステートが一度 pending から fulfilled か rejeted に変わったら、それ以上変更することはできません。

Promise の簡単な例

Promiseを使ったコードのちょっとした例を掲載しておきます。
8分茹でて、カチカチのゆで卵を作るお料理プログラムです。

let cookPromise = (food, minutes) => {
  return new Promise((resolve, reject) => {
    if (minutes > 7 && 9 > minutes) {
      resolve(`${food}が完成!`)
    } else {
      reject("オー!ノー!")
    }
  })
}
const cookingTime = parseInt(Math.random() * 10);
cookPromise('ゆで卵', cookingTime)
  .then((res) => {
    console.log(res); // ゆで卵が完成!
  })
  .catch((err) => {
    console.error(err); // オー!ノー!
  })
  .finally(() => {
    console.log('調理終了!');
  });

cookPromise が実行されたとき、resolveからレスポンスを受けて、then() メソッドを使い、rejectからレスポンスを受けて、catch() メソッドを使っています。そして、fulfiiled か rejected に関わらず、finally()を実行しています。

Promise のチェーン

promise.then(), promise.catch(), promise.finally() は新しい Promise オブジェクトを返すこともできます。

これにより、次のアクションごとにメソッドを変更できるので、コールバック地獄(ネストされまくった「運命のピラミッド」 (pyramid of doom) と呼ばれるもの)を回避することができます。

myPromise
  .then(handleResolvedA)
  .then(handleResolvedB)
  .then(handleResolvedC)
  .catch(handleRejectedAny);

Promise.all()

ゆで卵の例を取り上げましたが、ときには大勢のお客様に一気にゆで卵を調理したいときもあると思います。
Promise.all() メソッドは引数として反復する Promise を取り、結果のアレイを返します。

Promise.all([cookPromise('オムレツ', 8), cookPromise('目玉焼き', 8)])
  .then(res => console.log(res));
// ["オムレツが完成!", "目玉焼きが完成!"]

Promise.race()

Promise.race() は Promise のアレイを受け取り、最初に確定した Promise の結果を返します。fulfills か rejects かに関わらず、最初に定まった結果に応じて実行されます。

Promise.race([cookPromise('オムレツ', 8), cookPromise('目玉焼き', 8)])
  .then(res => console.log(res));
// 何でもいいから最初にでき上がったのを食べる

次のステップ

Promiseの基本と使い方を紹介しました。
JavaScriptの非同期処理について少しでも感触をつかんでいただけると幸いです。
さらに踏み込んで学びたい方は以下から、シンタックスシュガー (糖衣構文)として: Async と Awaitについて学んでみましょう!

await - JavaScript | MDN
await 演算子はプロミス (Promise) を待ち、履行値を取得するために使用します。非同期関数の中、またはモジュールの最上位でのみ使用することができます。

(サイ)


English version: Understanding JavaScript Promises

Hi, this is Tsai, a ShareWis software engineer from Taiwam.

I want to share the basic knowledge of JavaScript Promise object and how to use it.

Why do we need Promises?

Why do we need Promises? That is because JavaScript has 2 features:

  • Single Thread
  • Synchronous

It means that JavaScript only executes one thing at a time and one by one. So the user experience would be bad if we make our users wait for something running for a long time. ECMAScript 2015, also known as ES6, introduced the JavaScript Promise object.

What is JavaScript Promise Object?

A promise is an object to deal with asynchronous code. Promise.then() takes two arguments which are resolve and reject, a callback for success and another for failure. We call resolve(…) when what we were doing asynchronously was successful, and reject(…) when it failed. They are optional, so you can add a callback for success or failure only.

let doSomething = new Promise(function(resolve, reject) {
  // Something like uploading files which may take some time.
  resolve(); // when successful
  reject();  // when error
});

doSomething.then(
  function(value) { /* code if successful */ },
  function(error) { /* code if some error */ }
);

Promise Object Properties

A JavaScript Promise object has 3 states:

  • Pending: initial state, neither fulfilled nor rejected.
  • Fulfilled: the operation was completed successfully.
  • Rejected: the operation failed.

The default state of Promise is pending, once resolve() is called, the state will change to fulfilled, and when reject() is called, the state will be changed to rejected. Please note that only the result of Promise affects the state, and you can not modify the state by yourself. Once the state is changed from pending to fulfilled or rejected, it will not change anymore.

Basic Example

We want to have a program for cooking. Let’s say making perfect hard-boiled eggs is 8 minutes.

let cookPromise = (food, minutes) => {
  return new Promise((resolve, reject) => {
    if (minutes > 7 && 9 > minutes) {
      resolve(`${food} is perfect!`)
    } else {
      reject("Oh no!!!")
    }
  })
}
const cookingTime = parseInt(Math.random() * 10);
cookPromise('Boiled egg', cookingTime)
  .then((res) => {
    console.log(res); // Boiled egg is perfect!
  })
  .catch((err) => {
    console.error(err); // Oh no!!!
  })
  .finally(() => {
    console.log('Experiment completed');
  });

When the function cookPromise is executed, we use the method then() to receive the response from resolve, catch() to receive the response from reject. And we have finally(), either fulfilled or rejected, the specified callback function is executed.

Chained Promises

The methods promise.then(), promise.catch(), and promise.finally() return a new Promise object. We can prevent the callback hell (pyramid of doom) by chaining the methods to take associate further actions.

myPromise
  .then(handleResolvedA)
  .then(handleResolvedB)
  .then(handleResolvedC)
  .catch(handleRejectedAny);

Promise.all()

We sometimes want to cook many dishes at the same time. The Promise.all() method takes an iterable of promises as an argument and returns an array of the results.

Promise.all([cookPromise('Omelette', 8), cookPromise('Fried egg', 8)])
  .then(res => console.log(res));
// ["Omelette is perfect!", "Fried egg is perfect!"]

Promise.race()

The Promise.race() also accepts an array of promises, but returns the first promise that is settled. So it will return the first result no matter it fulfills or rejects.

Promise.race([cookPromise('Omelette', 8), cookPromise('Fried egg', 8)])
  .then(res => console.log(res));
// I will eat whatever got finished first.

Next steps

Today I give you a brief introduction to Promise and its usage. You are no longer unfamiliar with asynchronous processes and will be good at the entire flow control. The advanced topic is to use syntactic sugar: Async & Await.

await - JavaScript | MDN
The await operator is used to wait for a Promise and get its fulfillment value. It can only be used inside an async function or at the top level of a module.

(Tsai)

採用情報

株式会社シェアウィズでは、新しいメンバーの募集を積極的に行っています。フルタイムの勤務から学生インターンまで、ご関心をお持ちの方はお気軽にコンタクトしてください!

採用ページへ

開発
ShareWis Blog(シェアウィズ ブログ)