同期, 非同期, async, awaitって何・・?

sync-async-await プログラミング

※当ブログでは商品・サービスのリンク先にプロモーションを含みます。ご了承ください。

仕事で同期, 非同期, async, awaitとか色々出てくるんですが、いつも忘れてしまうのでまとめさせてください。

何回も忘れてしまう・・

参考

同期, 非同期とは

プログラムを組む上で、どのようにプログラムが動いているのかを理解することは大事です。

基本的に上から下へ処理が行われ、ある処理が終わってから次の処理へ移るのが基本的な動きなのだと私は理解していました。

同期処理とは

上から下へ処理が走り、ある処理が終わるまで次の処理へ移らない処理をいいます。

console.log("hello world")

for(var i=0; i<10; i++){
  if(i<3){
    console.log("i ", i);
  }
}

console.log("bye world")

実行結果

> "hello world"
> "i " 0
> "i " 1
> "i " 2
> "bye world"

非同期処理とは

上から下へ処理が走り、ある処理が終わるのを待たずに次の処理へ移る処理をいいます。

synchronos-async

うまく書けなかったです。ごめんなさい。同期は猫の顔を1個ずつ書いているけど

非同期の方では、猫の顔も描きつつ、体を非同期で書いています。。

同期処理だと、処理が終わるまでずっと待ってなくてはいけないけど、非同期処理ならば、待ってる間に違う処理を進めてくれるので、時間を無駄に使ってない感じがします。(私はせっかちなので・・)

jsだと、setTimeout関数は非同期処理をしてくれる関数なので、こちらを使って1秒待ってから処理を行うようにしてみます。

setTimeoutとは、指定した数だけ処理を遅らせてくれる非同期関数

console.log("hello world")

setTimeout(() => {
  for(var i=0; i<10; i++){
    if(i<3){
      console.log("i ", i);
    }
  }
}, 1000);

console.log("bye world")

実行結果

> "hello world"
> "bye world"
> "i " 0
> "i " 1
> "i " 2

同期処理の時とは違い、先にbye worldが出力されているのが分かります。

これが同期処理だとすると、1秒待ってから次への作業という無駄な時間が発生しますが、非同期処理であれば1秒待つ間に次の処理へ移ってくれるので、無駄作業が発生しないのがわかります。

並行処理と並列処理とは違うの?

ここもよくわからなくなるワードなんですが・・調べてみると、同期・非同期とは一緒に話すべきではないワードのようですね。

確かにググってみるとプロセスやスレッドなどの解説によくでてきます。なので、一旦忘れます。というか、プロセスやスレッドもまとめたいので、そのときの記事で解説します。

Promiseとは

async, awaitの話をする前に、Promiseをまず理解していないと難しいと思うので、Promiseから説明をさせてください。

Promiseとは、非同期処理の最終的な完了もしくは失敗を表すjsのオブジェクトです。

Promiseの状態

  • 待機 (pending): 初期状態。成功も失敗もしていません。
  • 履行 (fulfilled): 処理が成功して完了したことを意味します。
  • 拒否 (rejected): 処理が失敗したことを意味します。

重要なのは、fulfilledとrejectedです。

これはそれぞれ引数で、resolve, rejectedのコールバック関数として取ることができます。

new Promise((resolve, reject) => {
  console.log("Promise");
  //resolve("hoge");
  //reject("hoge");
}
).then((data) => 
  console.log("data is ", data)
).catch((e) => 
  console.log("error data is ", e)
).finally(()=>
  console.log("finally")
);

console.log("global end")

こちら、それぞれresolve, rejectが実行された場合の期待値を書いてみます。

resolve(“hoge”) を実行した場合

処理が成功して完了したパターンです。

Promise
global end
data is hoge
finally

reject(“hoge”) を実行した場合

処理が失敗したパターンです。

Promise
global end
error data is hoge
finally

というように、非同期処理を実現したい場合はPromiseを使う!ということが理解できました。

イベントループのメカニズム

少し話はそれますが、

【図解】1から学ぶ JavaScript の 非同期処理 で解説されている、イベントループのメカニズムはjsを使う身としてとても役立ちました。

jsがどう動いているのか基本的なことがあまりわかっていないという方は一回読んでおくことをおすすめします!

特にコールスタックとタスクキューの概念はためになりました。わけわからなくなるよ!って方は読み飛ばしてください。

コールスタックとは

関数が呼ばれるとここに積まれます、LIFOの構造を持っています。メインスレッドで実行されます。

タスクキューとは

非同期関数が呼ばれるとここに積まれ、FIFOの構造を持っています。メインスレッド外で実行され、

コールスタックが空になるまで、タスクキューは実行されません。

async, awaitとは

ここでついに登場するのがasync, awaitです。

先ほど説明しましたが、これらは非同期処理のPromiseをさらに直感的に書けるようにしたものです。

asyncとは

Promiseを返す関数の先頭につけます。ルールです!

awaitとは

awaitをつけることで、非同期処理であるasync関数の処理を同期処理っぽく処理することができます。

また、awaitを使ってる関数では、asyncを関数の先頭につける必要があります。

コードを見てみましょう。

const promiseFunc = () => new Promise((resolve, reject) => {
  console.log("Promise");
  resolve("hoge");
}
).then((data) => {
  console.log("data is ", data);
  return data;
}
).catch((e) => 
  console.log("error data is ", e)
).finally(()=>
  console.log("finally")
);

const init = async () => {
  console.log("init 1");
  await promiseFunc();
  console.log("init 2");
}

init();

console.log("global end");

こちらは、何が出力されると思いますか?

実行結果

> "init 1"
> "Promise"
> "global end"
> "data is " "hoge"
> "finally"
> "init 2"

init 2が実行されるまでに、Promiseの処理が全て実行されているのがわかります。これは、同期処理のような動き方をしてますよね?

global endがなぜ、ここに入ってくるの?

こちらはコールスタックに入っているので、先に実行されます。

では、await promiseFunc()awaitを取り除いてみるとどうなるでしょう?

const promiseFunc = () => new Promise((resolve, reject) => {
  console.log("Promise");
  resolve("hoge");
}
).then((data) => {
  console.log("data is ", data);
  return data;
}
).catch((e) => 
  console.log("error data is ", e)
).finally(()=>
  console.log("finally")
);

const init = () => {
  console.log("init 1");
  promiseFunc();
  console.log("init 2");

}

init();

console.log("global end");

実行結果

> "init 1"
> "Promise"
> "init 2"
> "global end"
> "data is " "hoge"
> "finally"

先ほどとは違い、非同期処理をしていることがわかります。

awaitは必ずつけなきゃいけないの?

私は今まで、Promiseを返す関数には必ずawaitをつけなくてはいけないものと思っていました。

ですが、それは間違っていて、awaitをつけることで非同期関数を同期関数っぽく処理することができるのであって、非同期処理のままがいいんだ!って時はawaitをつけなくてもいいんですね。

まとめ

非同期, 同期, async, awaitの説明をしました。いままで曖昧でしたが、ようやく分かった気がします。

次はスレッド、プロセス、並列、並行処理あたりをまとめたいです。曖昧なので。

にほんブログ村に参加しております!よろしければクリックお願いします 🙂

にほんブログ村 IT技術ブログへ
にほんブログ村

コメント

タイトルとURLをコピーしました