*noteかqittaどっちがいいのかわからないので、確認のため同じ記事をnoteでも出しています
noteの方
#Async/Awaitとは?
まず、Async/Awaitは「"A"という処理が終わり次第"B"」という同期的な処理を行うときに使うやつです。「○○というファイルを読み
込んでから、○○という処理を行う」や複雑なアニメーション処理などで使われます。
特にAsync/Awaitが特に必要とされる場面はサーバーサイドjsを使ってDB接続や外部のクラウドのサービスを使ったりするときです。Async/Awaitを使えないとかなりキツイと思われるので、是非この記事で覚えていただければなと思います。また、C#やTypescriptなどの他の言語でもAsync/Awaitは使えます
今回はAysnc/Awaitを使って実際に簡単なアニメーションの処理を作ることで解説します(※素のjsで書くのは面倒くさいのでjQueryを使用します)
今回作るアニメーションの完成品はこちらです
https://sakaij.github.io/asycn-await
Async/Awaitを使わない例
Async/Awaitは同期的な処理を行う場合に使うやつと言いましたが、実はAsync/Awaitを使わなくても、実現できてしまいます。
"春はあけぼの"というテキストの入ったp要素を表示させるプログラムを作るとすると
$('.春はあけぼの').fadeIn();
このような感じで作れると思います。
次に"春はあけぼの"が表示された1秒後に、「やうやうしろくなりゆく山ぎは」を表示させるプログラムを作ると、
$('.春はあけぼの').fadeIn();
setTimeout(() => {
$('.やうやうしろくなりゆく山ぎは').fadeIn();
}, 1000);
こうなると思います。
Async/Awaitを使わなくても、「"A"という処理が終わり次第"B"」ができてしまいました・・・
「Async/Awaitを使わなくてできるなら、使わなくていいじゃないか」と思われるかもしれないですが、確かにこの程度であれば使わなくても全く問題ないですがこれからが問題になります。
さらに、その後に"すこしあかりて、紫だちたる雲のほそくたなびきたる"を表示させるとどうなるでしょうか
$('.春はあけぼの').fadeIn();
setTimeout(() => {
$('.やうやうしろくなりゆく山ぎは').fadeIn();
setTimeout(() => {
$('.すこしあかりて紫だちたる雲のほそくたなびきたる').fadeIn();
}, 1000);
}, 1000);
こうなりますが、かなり見づらくないでしょうか?
さらに、これが"夏は夜"、"月の頃はさらなり"・・・と続くと地獄のようなコードになります
$('.春はあけぼの').fadeIn();
setTimeout(() => {
$('.やうやうしろくなりゆく山ぎは').fadeIn();
setTimeout(() => {
$('.すこしあかりて紫だちたる雲のほそくたなびきたる').fadeIn();
setTimeout(() => {
$('.夏は夜').fadeIn();
setTimeout(() => {
$('.月の頃はさらなり').fadeIn();
}, 1000);
}, 1000);
}, 1000);
}, 1000);
これが、Async/Awaitを使わないことの問題点です。
Async/Awaitを使用するとどうなるか?
先ほどのコードにAsync/Awaitを使うとどうなるでしょうか?
Before
$('.春はあけぼの').fadeIn();
setTimeout(() => {
$('.やうやうしろくなりゆく山ぎは').fadeIn();
setTimeout(() => {
$('.すこしあかりて紫だちたる雲のほそくたなびきたる').fadeIn();
setTimeout(() => {
$('.夏は夜').fadeIn();
setTimeout(() => {
$('.月の頃はさらなり').fadeIn();
}, 1000);
}, 1000);
}, 1000);
}, 1000);
After
function timer(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
}
(async function(){
$('.春はあけぼの').fadeIn();
await timer(1000);
$('.やうやうしろくなりゆく山ぎは').fadeIn();
await timer(1000);
$('.すこしあかりて紫だちたる雲のほそくたなびきたる').fadeIn();
await timer(1000);
$('.夏は夜').fadeIn();
await timer(1000);
$('.月の頃はさらなり').fadeIn();
await timer(1000);
})();
このようなすっきりと、何が起きているかわかりやすいコードにすることができます
さて、Async/Awaitの解説に入りたいところですが、Async/Awaitと切っても切り離せないのがPromiseというオブジェクトです
簡単にPromiseとAsync/Awaitを説明すると、
Promise
・・・「"A"という処理が終わり次第"B"」という同期的な処理を行うためのオブジェクト
Async/Await
・・・Promiseをさらに、使いやすくするためのやつ(演算子)
で、
素のjs < Promise < Async/Awaitと考えてもらえればいいです。
なので、Async/Awaitを解説する前に、Promiseの解説をしなければなりませんので、必要最低源必要なPromiseの解説をします。
Promiseの解説
まずは、先ほどの素のjsのコードをPromiseで書き換えてみます
$('.春はあけぼの').fadeIn();
setTimeout(() => {
$('.やうやうしろくなりゆく山ぎは').fadeIn();
setTimeout(() => {
$('.すこしあかりて紫だちたる雲のほそくたなびきたる').fadeIn();
setTimeout(() => {
$('.夏は夜').fadeIn();
setTimeout(() => {
$('.月の頃はさらなり').fadeIn();
}, 1000);
}, 1000);
}, 1000);
}, 1000);
をPromiseを使って書き直すと
function timer(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
}
new Promise((resolve, reject) => {
resolve()
})
.then(() => {
$('.春はあけぼの').fadeIn();
return timer(1000);
})
.then(() => {
$('.やうやうしろくなりゆく山ぎは').fadeIn();
return timer(1000);
}).then(() => {
$('.すこしあかりて紫だちたる雲のほそくたなびきたる').fadeIn();
return timer(1000);
}).then(() => {
$('.夏は夜').fadeIn();
return timer(1000);
}).then(() => {
$('.月の頃はさらなり').fadeIn();
return timer(1000);
});
と先ほど説明したように、Async/Awaitよりは見づらいが素のjsよりは見やすいコードになります。
上のコードを順々に解説していきます。
①Promiseオブジェクトを生成
まず処理のはじめの
new Promise((resolve, reject) => {
resolve()
})
.then(~
は何をしているかというとPromiseオブジェクトを生成しています。Promiseオブジェクトにはthenというメソッドがついてるので、thenメソッドを呼び出しています。
resolve()というのは後ほど解説します。先ほどのコードをもっと丁寧に書くとこうです。
var ps1 = new Promise((resolve, reject) => {
resolve()
});
ps1.then(
thenの引数には次に行いたい処理を書きます。
.then(()=>{
//次に行いたい処理
})
Promiseにおける処理は、Promiseオブジェクトのthenを繋げて呼び出し続けることで同期的な処理を実現します。
.then(()=>{
//次に行いたい処理
})
.then(()=>{
//次に行いたい処理
})
.then(()=>{
//次に行いたい処理
})
・・・
then().then().then()のように繋げられるのはthen()はPromiseオブジェクト返すからです。
var ps = new Promise((resolve, reject) => {
resolve()
})
.then(()=>{});
//psはPromiseオブジェクト
console.log(ps);をすると、Promiseオブジェクトが出力されるはずです。
そして、この関数の中ではPromiseオブジェクトを返すことで、次のthenに繋げるタイミングを管理できます。
.then(()=>{
$('.春はあけぼの').fadeIn();
return timer(1000);
})
のように、timerを返しています。
function timer(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
}
timerの中身はこれでした。つまり書き換えると、
.then(()=>{
$('.春はあけぼの').fadeIn();
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
});
})
で、thenの中で、Promiseオブジェクトを生成して、返してますね?
そして、resolveというのが先ほどから出てきてますが、resolve()というのは**「今回の処理は終わったので、次のthen()の処理を始めてくれ」**という意味であり、今回は、1秒後にresolve()を呼んで次のthen()の処理に繋げています。
なので、一番最初の処理の
new Promise((resolve, reject) => {
resolve()
}).then(~
は、Promiseオブジェクトを生成して何の処理もせずに、即時resolve()を読んでいるので、すぐに次のthen()に繋がります。
もちろん、ここで
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 5000);
}).then(~
のように処理をしてもいいです。この場合5秒間待って一番最初の「春はあけぼの」が出てきます。
↑の場合は、↓のようにすることもできます。関数timerの戻り値はPromiseオブジェクトなので
timer(5000).then(~
追記:
ややこしくなるかなと思ったので、載せてなかったんですが、即resolve()するPromiseを生成するならPromiseの静的メソッドである「Promise.resolve();」を使っても、同様の効果が得られます。つまり「Promise.resolve().then(~」ですね
「new Promise(~」の方がPromiseオブジェクトを生成して、resolve()してるというのが分かりやすいので使ってます。
※resolve()の中で関数を呼び出した時の例外処理に挙動の違いがあるようですが、とりあえず気にしなくてもいいと思います。
Async/Awaitの解説
Promiseの解説が終わったので、やっとAsync/Awaitの説明ができます。
先ほど記述した
function timer(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
}
(async function(){
$('.春はあけぼの').fadeIn();
await timer(1000);
$('.やうやうしろくなりゆく山ぎは').fadeIn();
await timer(1000);
$('.すこしあかりて紫だちたる雲のほそくたなびきたる').fadeIn();
await timer(1000);
$('.夏は夜').fadeIn();
await timer(1000);
$('.月の頃はさらなり').fadeIn();
await timer(1000);
})();
ですが、
まず、awaitの説明から入ります。Promiseオブジェクトの前にawaitをつけることで、**そのPromiseオブジェクトがresolve()を行うまで待機させることができます。**もし、Promiseオブジェクトではないものにawaitをつけても、何も起こらないです。
await timer(1000);
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
});
この2つは同義です。
次に、Asyncの説明に入ります。Asyncは関数の前につけると、Promiseオブジェクトを返すようになります。Awaitを使う関数には、必ずつけなければなりません。
とりあえずはAwaitを使う関数の前につけるやつという認識で大丈夫だと思います。
(async function(){
$('.春はあけぼの').fadeIn();
await timer(1000);
$('.やうやうしろくなりゆく山ぎは').fadeIn();
await timer(1000);
$('.すこしあかりて紫だちたる雲のほそくたなびきたる').fadeIn();
await timer(1000);
$('.夏は夜').fadeIn();
await timer(1000);
$('.月の頃はさらなり').fadeIn();
await timer(1000);
})();
今回では、即時関数を使っており、その関数の前にasyncを定義しています。書き直すと
async function output(){
$('.春はあけぼの').fadeIn();
await timer(1000);
$('.やうやうしろくなりゆく山ぎは').fadeIn();
await timer(1000);
$('.すこしあかりて紫だちたる雲のほそくたなびきたる').fadeIn();
await timer(1000);
$('.夏は夜').fadeIn();
await timer(1000);
$('.月の頃はさらなり').fadeIn();
await timer(1000);
};
output();
こうなります。output();には、Promiseオブジェクトが格納されているので
async function output(){
$('.春はあけぼの').fadeIn();
await timer(1000);
$('.やうやうしろくなりゆく山ぎは').fadeIn();
await timer(1000);
$('.すこしあかりて紫だちたる雲のほそくたなびきたる').fadeIn();
await timer(1000);
$('.夏は夜').fadeIn();
await timer(1000);
$('.月の頃はさらなり').fadeIn();
await timer(1000);
};
await output();
このように、ouput();に対してawaitをつけることもできます。ですが、先ほども言ったように、awaitは必ずAsyncをつけた関数の中でしか利用できません。
なので、output()にもawaitをつけたい場合は、
async function output(){
$('.春はあけぼの').fadeIn();
await timer(1000);
$('.やうやうしろくなりゆく山ぎは').fadeIn();
await timer(1000);
$('.すこしあかりて紫だちたる雲のほそくたなびきたる').fadeIn();
await timer(1000);
$('.夏は夜').fadeIn();
await timer(1000);
$('.月の頃はさらなり').fadeIn();
await timer(1000);
};
(async function(){
await output();
})();
このように、してasyncの関数で囲ってやる必要があります。
最後に
今回は、最低限Async/Awaitが書けるようにという趣旨の記事です。なので、省略している説明は多いです。例えば、resolveの隣のrejectも気になったとは思いますが、詳しい説明はまた別の記事でしようと思います。
また、外部サービスと接続するためのライブラリを使うとき、接続するためのメソッドなどではPromiseを返す設計になってることが多いので、そのメソッドの戻り値が何なのか確認してみてください。
もちろん、Promiseを返す設計になっておらず、コールバックのみしか用意していない場合もあるでしょう。そういう場合は
await new Promise((resolve,reject)=>{
yomikomi((data)=>{
console.log(data);
resolve();
});
});
のような形で、自分でPromiseを作ってしまって対処できます。
あと、ブラウザの種類やバージョンによってはAsync/AwaitやPromiseがそもそも使えない場合があります。PromiseはES6(2015)、Async/AwaitはES7(2017)でないと動かないので、babelなどでトランスパイルすることをお勧めします。
そのままで使うとどちらも、IEでは動きません...
githubでも公開しているので、セレクタや書き方はちょっと変えてますが、ぜひご覧ください
https://github.com/Sakaij/asycn-await/