PromiseやAsync/awaitという言葉をよく耳にするようになってきましたが、どういう仕組みか理解せず苦しんでいる方も多いのではないでしょうか?
実は自分もこの記事書くまではかなり苦戦していました…!
そこで、できるだけ、本当にできるだけわかりやすく説明するために、いろんなケースを交えて説明していこうと思います!
そもそもどういうときにPromiseって必要になるんや?
まず以下のコードをご覧ください
function getData () {
return axios.get('http://example.com')
.then((res) => {
return res;
})
}
let data = {};
if (data) {
data = getData ();
}
console.log(data);
上記のコードを実行すると、コンソールに帰ってくるのはexample.comのデータ…ではなくて、promiseが帰ります。
ここで、なぜpromiseにしているのか?と疑問に思った人も何人かいると思います。
もともと、javascriptは上から順番に実行されるとは限らない言語仕様になっています。
そのため、axiosのレスポンスよりも先に下のコードが実行されてしまう可能性があり、結果としてデータが何も入っていないまま帰ってしまうことがあるのです。
この現象を防ぐための役割を果たしているのがPromiseです
Promiseを使うことで、順番を指定することができます。
それでは、一体どのようにPromiseを使うのでしょうか?
実際にPromiseを使ってみる。
定義編
それでは、Promiseをつかってどのように順番を指定していくのかを見てみましょう。
基本的な使い方は、以下のコードのとおりになります。
function getData () {
return new Promise(function(resolve, reject) {
try {
resolve('success!') // 成功したときの処理をここに書きます。
} catch (e) {
reject('error...') // 失敗したときの処理をここに書きます。
}
})
}
console.log(getData());
話をシンプルにするため、先程よりもコードをシンプルにしました。
関数の中でPromiseクラスをリターンしています。先程のaxiosの戻り地も、元はこのpromiseクラスが帰ってきています。
もしオブジェクト指向を理解していたら、このオブジェクトのインスタンス化する際の引数に、コールバック関数を定義してあげることで使えるようになります。
Promiseに必要な引数は第一引数のみです。コールバック関数の中で定義できる関数は2種類あります。
- resolve:成功したときにreturnさせる値を引数に入れます。今回はsuccessという文字列を返しました。
- reject:失敗したときにreturnさせる値を引数に入れます。今回はerror...という文字列を返しました。
上記のプログラムを実行すると
Promise { 'success!' }
上記のようなpromiseが帰ります。
promiseが帰ったときは、promiseを展開してあげることで中身のデータを取り出せるようになります
呼び出し編
先程のPromiseを呼び出すには、axiosを使っているときと同じように、thenとcatchを使ってあげます。
function getData () {
return new Promise(function(resolve, reject) {
try {
resolve('success!') // 成功したときの処理をここに書きます。
} catch (e) {
reject('error...') // 失敗したときの処理をここに書きます。
}
})
}
// 実際にgetDataの中身を取り出す
getData()
.then(function (resolve) {
console.log(resolve)
})
.catch(function (reject) {
console.log(reject)
})
console.log('finish?')
引数とメソッド名を見るとなんとなく察した方がいると思います。
getDataメソッドを呼び出した際、成功した処理をthenに、失敗したときの処理をcatchに書きます。
また、それぞれの関数の引数は、成功したときはresolve、失敗したときはrejectが入ります。このコードから、getDataメソッドを定義している側と関連づいていることがわかりますね。
実際に上記のコードを実行をしてみると、一番最初に表示されたのはfinish?で、その後にsuccess!が表示されました。
なぜこの順番になるかというと、promiseの引数に渡されたコールバック関数内で、thenだったらresolve、catchだったらreject関数が呼ばれるまでは次の処理に行かないという特性があります。
ただし、getDataメソッドの下にあるconsole.log('finish?')は、promiseのスコープにないため、promise内の処理が完了し切る前に先にconsole.log('finish?')が実行されていまいます。
finish?を一番最後に表示させるにはどうしたらいいか?promiseを使えばこの問題も簡単に解決できます。
function getData () {
return new Promise(function(resolve) {
setTimeout(function(){ resolve('success!'); }, 1000);
})
}
getData()
.then(function(data) {
console.log(data);
return new Promise(function(resolve) {
resolve(true);
});
})
.then(function (checkBool) {
if (checkBool) {
console.log('finish!');
}
});
show関数のresolveが呼び出された後にif (checkBool)が走ることを保証されているため、これで順番の秩序を保つことができました!
えーでもpromise定義するたびにコード肥大化しちゃうじゃーん
安心してください。ちゃんとスマートに書くためのアプローチがすでに用意されています。
ただ、ここから先の内容を理解するには、上記のPromiseのルールを頭に叩き込んでおく必要がありますので、まだ理解していない方はもう一度読み返すか、別のPromise関連の記事を参考にしてみてください。
##async/awaitを使ってみる
上記のコードをasync/awaitを使ってシンプルにさせてみましょう。
async/awaitに置き換えるときのコツは以下です。
- return new Promiseを外し、代わりにfunctionの左にasyncをつける(例:async function getData() {}
- resolveやrejectにはreturnを使う。(1つめのawaitはresolve、2つめのawaitはreject)
これらを踏まえた上で書き換えてみると以下のとおりになります。
async function getData () {
try {
return 'success!' // 成功したときの処理をここに書きます。
} catch (e) {
return 'error...' // 失敗したときの処理をここに書きます。
}
}
async function show() {
getData()
.then(function (resolve) {
console.log(resolve)
})
.catch(function (reject) {
console.log(reject)
})
return true
}
show()
.then(function (checkBool) {
if (checkBool) {
console.log('finish!')
}
})
少しだけスッキリしました。もう少しスッキリさせてみましょう。
- asyncを使っている関数内でpromise呼び出しているところがあれば、then,catchをなくし、代わりに変数を一つ用意してawaitを使って代入する
- 通常定義でasync関数の中にない場合は、awaitを使えないので普通にthen,catchを使ったまま
少しイメージつきづらいと思うので、これに関してはソースコードを見たほうが早いかもしれません。
async function getData () {
try {
return 'success!' // 成功したときの処理をここに書きます。
} catch (e) {
return 'error...' // 失敗したときの処理をここに書きます。
}
}
async function show() {
let data = await getData()
console.log(data)
return true
}
show()
.then(function (checkBool) {
if (checkBool) {
console.log('finish!')
}
})
だいぶスッキリ書くことができましたね!
まとめ
この記事を書きはじめる数分前に理解したばかりで、思考の整理をしながら記事に起こしてみたのですが、この記事が誰かの役に立つといいなと思っています。
※文章書くの苦手すぎる為読みづらくてごめんなさい💦編集したほうがいい箇所ありましたら遠慮なくお申し付けくださいm(_ _)m