deve.K

Hello, Welcome

English

JavaScript 基礎 PromiseとAsync/Await

f:id:dev04K:20211201185329j:plain

PromiseとAsync/Await

前回の記事に引き続きJavaScriptの基礎で難関ポイントである、Promiseを重点的に攻めていきましょう。 その後に軽くAsync/Awaitにも触れていきます。

同期処理・非同期処理

Promiseを理解するにはまずは

同期処理・非同期処理の理解が出来ているかが 前提となっています。

なんだっけ?なんとなくは理解してる...。 そんな方にざっと説明します

理解が完璧な方は下部の

promiseへ移動してください。

そもそもプログラムというのは上から順番に処理がされていきます、そして処理が1つ1つ完了するまで待ちます、これが同期処理と言います。 あなたが書いたプログラムの順番通りに処理が進みます。

例えば、私とあなた二人で料理をしたとしましょう、私は食材を炒めるのであなたは食材のカットをお願いします。

あなたが切ってる間、私は何をしているかと言うと放置状態です見てるだけですあなたが切り終わるまでひたすら待機、切るのが終わってようやく次の処理である私となります。

処理1

処理2 『1が終わるまで停止状態』

処理3 『2が終わるまで停止状態』

なので食材を切るという処理が停止するまで どんな事もしてはいけない、これが本来のプログラムによる同期処理なのです。

非同期処理

先ほどの同期処理での料理の例えでこう思われたかと、これが現実なら非効率すぎる!!

ここで非同期処理です、あなたがカットしてる間に私は別の作業などをします。

つまり食材カット処理の完了を待つ事なく次の処理の結果を受け取ることができるのです。

それが同期処理と非同期処理との違いになります。

非同期関数では処理の完了順序をコントロールできない問題もあります。

しっかりと

処理1

処理2

処理3

完了する場合もあれば

処理1

処理3

処理2

function Cook1() {
setTimeout(function () { 

console.log("カット処理");

    }, 3000);
}

function Cook2() {
setTimeout(function () { //

console.log("炒める処理");

    }, 3000);
}

function Cook1() {

console.log("料理完成処理");

}
//実行の順番
Cook1(); 
Cook2(); 
Cook3(); 
1. 料理完成処理
2. カットの処理
3. 炒める処理

という並びとはバラバラに完了する場合もあります 順番通りにコードを書いて、それ通りに処理がされないのは困りますよね!

setTimeout関数は非同期で動く関数となります。

詳しくはまた別の記事として取り上げます。

コールバック関数での非同期処理

コールバックとは関数の引数に関数を渡し呼ばれることですが

本来の非同期処理と上手く組み合わせていき使われていきます。

引数に完了したあとの処理を送り

完了したあとに行いたい処理を指定していきます。

ですがコールバックは便利で万能な反面、乱用しすぎるとコードのネスト(入れ子)が深くなり、かなり読みづらく何がなんだか分からなくなってきます。

Sample1(function(sampledata1) {

 Sample2(function(sampledata2) {

   Sample3(function(sampledata3) {

     Sample4(function(sampledata4) {
                //処理

            });

        });

    });

});

こちらはあくまでも例ですが

JavaScriptにおける非同期処理のコールバック関数はこのネスト(入れ子)により関数内の処理が複雑化していき

可読性が低くなったのをコールバック地獄とエンジニア達は言ったりします。

そして非同期処理におけるコールバック地獄や実行結果の順序を解消するためにPromiseが導入されます。

Promise

お待たせしました!promise入りましょう

純粋に処理の流れを解説していきます。

Promiseは非同期処理の最終的な完了or失敗を表すオブジェクトです

非同期処理でのコールバック関数の可読性を向上させ非同期処理の操作がより簡単にそして簡潔に記述できる仕組みです。

Promiseでは最終的に渡したい値の代わりに『プロミスオブジェクト』を返しておきます

とりあえずこれ渡しておくね!って感じです

そして渡したい値を返せる状態になった時に そのプロミスオブジェクトを通して呼び出し元にその値を返してくれます。

まずはPromiseの3つの状態を理解してください、必須です。


  • pending: 待機(初期状態)処理が終わっておらず成功・失敗どちらでもない

  • fulfilled: 処理の完了や成功の状態

  • rejected: 拒否となり処理の失敗の状態

これら3つの値を表すオブジェクトとなる。


サンプルコード用意しました。

const PromiseTest = new Promise((resolve, reject) => { // 1
  console.log('処理1')
  resolve('Hello ')
})

PromiseTest.then((msg) => { // 2
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('処理2')
      resolve(msg + "I'm")
    }, 500)
  })

}).then((msg) => { // 3
  console.log('処理3')
  return msg + 'dev.K.'

}).then((msg) => { // 4
  console.log('処理4')
  console.log(msg)

}).catch(() => { // エラー
  console.error('Unexpected error!!!')
})
処理1

処理2

処理3

処理4

Hello I'm dev.K

ではpromiseでの処理の流れを解説します。 promiseは先程の3つの値でのオブジェクトと言いました、上部コードを元になるべくざっくり短めに説明していきます。

  • pending: 待機(初期状態)処理が終わっておらず成功・失敗どちらでもない

  • fulfilled: 処理の完了や成功の状態

  • rejected: 拒否となり処理の失敗の状態

promiseの引数には関数を渡し、その第一引数にresolveを渡し、第二引数にはrejectを渡します(任意) コンソールで確認するとpromiseオブジェクトが返ってくるので試してみてください。

Promiseオブジェクトですが、定義された時点でpending(待機状態)です。

なのでPromiseTestがNew演算子によってpromiseオブジェクトを作成された時はまだpendingとなります。

それからコールバック内でresolve()を渡されてFulfilled状態へ移ります、処理の完了や成功の状態となります。

その後に関数が実行されpromiseオブジェクトの状態は変化します。

then()メソッドへと移行され非同期として実行されていきます

thenメソッドはpromiseオブジェクトに渡された コールバック関数の処理の結果を取得し渡された処理をthenによってreturnで返しpromiseは値を解決します。

thenメソッドにより複数の非同期処理を順番に実行したり成功・失敗の分岐が可能となり コールバック地獄を回避し、 可読性を向上させる事ができるメソッドチェーンになります。

逆にコールバック内でreject(処理の失敗)が呼ばれた場合は Rejected状態となり catch()が実行されエラーが返ってきます。

このコールバック内のresolveとrejectがどちらも呼び出されなかった場合はどうなるのか

pending(待機)状態となり、もちろん処理のチェーンにもなりません。 コールバックが実行され1度pending状態へと戻ってしまうと変化が起きる事はなくなり戻る事はありません。

という事はresolve・rejectは1度きりの実行という事になります。

処理の流れはざっくりですがこんな感じです。 promiseは奥深いので色々なやり方がありますぜひ調べながらやってみてください。

Async/Await

さきほどのpromiseでのメソッドチェーンでの非同期処理・同期処理をさらに効率良くわかりやすく記述する事が可能なのがAsync/Awaitです。

基本的にはAsync/Awaitを使用してるので覚えるなら間違いなくこれです。

先程のpromiseでのメソッドチェーンを見てみましょう

}).then((msg) => { // 3
  console.log('処理3')
  return msg + 'dev.K.'

}).then((msg) => { // 4
  console.log('処理4')
  console.log(msg)

}).catch(() => { // エラー
  console.error('Unexpected error!!!')
})

これは全然分かりやすいですが、これがどんどんつながれていき深くなったらどうでしょうか

}).then((msg) => { // 3
  console.log('処理3')
  return msg + 'dev.K.'

}).then((msg) => { // 4
  console.log('処理4')
  console.log(msg)

}).then((msg) => { // 5
  console.log('処理5')
  return msg + 'dev.K.'

}).then((msg) => { // 6
  console.log('処理6')
  console.log(msg)

}).then((msg) => { // 7
  console.log('処理7')
  return msg + 'dev.K.'

}).then((msg) => { // 8
  console.log('処理8')
  console.log(msg)

}).catch(() => { // エラー
  console.error('Unexpected error!!!')
})

『promise.all』メソッドを使用すれば良くはなりますが、やはりもう少し見やすく効率的にならないかな!!って事でAsync/Awaitです

せっかくなんでAPIのfetchで一緒に学んでしまいましょう!。

const getId = async () => {

   const message = 'UserDeta';
   
   const url = 'https://jsonplaceholder.typicode.com/users';

const json = await fetch(url).then(res => {

return res.json();

}).catch(error => {

console.error("非同期失敗", error);

return null
});

console.log(message, json)

}

getId(); //実行

promiseを理解できてきているならAsync/Awaitはそこまで理解するのは苦労しないはずです。

まずは『async』を関数の前に必ず宣言しますasyncはその関数内は非同期である事を示す関数となります つまりpromiseオブジェクトを返します。

上部のコードではアロー関数で分かりずらいかも知れませんが関数を定義する前にかならずasyncを記述して下さい。

『await』はasync関数内でないと使用する事はできません、セット物です

promiseでの結果が返ってくるまでasync関数内での処理を一時停止します。 そして結果が返ってきて、処理をまた再開します。

その後fetchメソッドを使用しサーバー上にあるデータを非同期でthenメソッドで実行していきます。

エラーの場合はcatch文でNullを返します。

Async/Awaitは他にもthenを使わずに非同期処理を行ったりPromise.all()の時とは違う並列処理などありますし人によって非同期処理の書き方が違います。

理解するまでは中々難しいとは思いますが、1度理解するとかなりのスキルアップに繋がりますのでぜひ頑張ってみて下さい。


 

私が独学で学び始めた時にお世話になった

狩野様の入門書です、第2版になります。

すごく分かりやすいのでオススメします。