Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
27
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

【JavaScript】Promiseをしっかり理解したい【その1】

初めに

今まで非同期処理の実装はasync awaitを使用してきましたが、根本的な理解ができておらず非同期処理の実装の度に頭にハテナがついて回っています。なのでまずはPromiseの理解を深め、まとめていきたいと思います。

Promiseとは

Promise オブジェクトは非同期処理の最終的な完了処理 (もしくは失敗) およびその結果の値を表現します。

PromiseはJavaScriptのビルトインオブジェクトであり、JavaScriptが動作する環境であればどこからでも呼び出すことができます。その実体は非同期処理を抽象化したオブジェクトであり、非同期処理を操作するための仕組みでもあります。

またPromiseはコンストラクタ関数であり、オブジェクトを生成、使用する際にはnewでインスタンス化します。

promiseオブジェクト生成
const promise = new Promise((resolve, reject) => {
    asyncFunc(){...}
});

コールバック関数との比較

非同期処理といえばコールバック関数で行う方法があると思います。

コールバックによる非同期処理
asyncFunc(arg, (error, result) => { 
    // 最初に実行されるべき処理
    if (error) { // コールバック失敗時の処理
        throw error;
    }
    // コールバック成功時の処理
});

asyncFuncを実行した時、asyncFunc関数内の最初に実行されるべき処理が完了した後に第二引数で指定したコールバック関数が実行され、処理順番の担保を取ることができます。

コールバック地獄

上記のようにコールバック関数が1つであれば、まだ簡潔で処理が追いやすいかと思います。しかし、非同期処理が何個もある場合、コールバック関数では入れ子が深くなり何をやっているのか一見した限りではわからないということがあります。このような問題をコールバック地獄といいます。

コールバック地獄
syncFunc((data) => {
  // 初めに実行される処理
  nextFunc((data) => {
    // 次に実行される処理
    nextNextFunc((data) => {
      // 次の次に実行される処理
      finalFunc((data) => {
        // 次の次の次に実行される処理
      }); // finalFunc
    }); // nextNextFunc
  }); // nextFunc
}); // syncFunc

実際は一つ一つの処理が何行かに渡って書かれるわけですから1つの関数がかなり肥大化してしまい、これに後から手を付けるとなったら、少しだけ現実から目を背けたくなるかと思います。

これをPromiseで記述すると以下のようになります。

Promiseによるコールバック地獄の解消
function asyncFunc(data) {
  return new Promise((resolve, reject) => {
    // 初めに実行される処理

    resolve(hoge);
  });
}

// nextFunc以下もasyncFuncと同様にPromiseオブジェクトをreturnする

...

asyncFunc(data).then((result) => {
  return nextFunc(data);
}).then((result) => {
  return nextNextFunc(data);
}).then((result) => {
  return finalFunc(data);
})

Promiseを使用する場合、非同期処理をasyncFuncのように関数としてまとめます。その関数では、Promiseオブジェクトが戻り値として返されるように記述します。その後の処理の流れはまた次回にまとめたいと思いますが、軽く説明するとPromise内でresolveされた場合、Promiseのインスタンスメソッドであるthenに処理が移ります。そしてそのthenメソッドの中で次に実行したいnextFunc関数を実行します。省略していますがnextFunc関数でもPromiseオブジェクトをreturnし、Promise内でresolveされ、次のthenメソッドに移る、という流れになりこうして実行の順番を確保することができます。

コールバック関数と比較すると、一つ一つの処理が小分けにされ何が返ってきてから次の関数が実行されるかが明確になり、修正を加える時などに手をつけやすくなったかと思います。処理の順番も追いやすいですね。

書き方

コールバック関数は書き方に明確なルールはありません。自由に記述することができます。ただ、それ故に統一性が失われてしまう可能性もあります。先程のソースコードをもう一度確認してみましょう。

コールバックによる非同期処理
asyncFunc(arg, (error, result) => { 
    // 最初に実行されるべき処理
    if (error) { // コールバック失敗時の処理
        throw error;
    }
    // コールバック成功時の処理
});

JavaScriptでのコールバック関数の第一引数にはErrorオブジェクトを渡すという慣例があります。ただし、先述した通りコールバック関数には明確なルールはなく、この慣例はあくまでコーディングルールなので開発者によって様々な書き方になります。

対してPromiseには、コールバック地獄の例でも見たように、成功時の処理と失敗時の処理のそれぞれに関数が用意されており、その用意されているメソッドthencatch以外は使用することができないので、書き方が統一されます。つまり、Promiseで書き方が統一され、Promiseによる様々な非同期処理のパターン化できることがPromiseのメリットの1つになるでしょう。

まとめ

まだPromiseに関して序盤もいいとこですが、思ったより長くなってしまったので次回投稿に回したいと思います。初めにも書きましたが開発の非同期処理でいきなりasync awaitを使って実装したため、理解が足りていないところが多く応用がきかないという状況に陥っていたので今回の記事をまとめさせてもらいました。既に実装で使っているからか、このまとめだけでも今まで疑問に感じていたところが解消されたので良かったです。ただ、Promiseの理解、という観点としてまだまだ足りなさ過ぎるので、次回も引き続き同じテーマで学習していきたいと思います。

次回

【JavaScript】Promiseをしっかり理解したい【その2】

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
27
Help us understand the problem. What are the problem?