非同期処理/同期処理について
C言語やPythonから入った私はコードは上から順に実行されるものだと思っていました。しかし、JavaScriptはクライアントサイドの言語であり、 「ページ更新などレスポンスの即時性が求められているため非同期処理である」 とのことです。
~同期処理とは~
コードを上から順に処理していき、「サーバとのデータのやりとりなど時間がかかる作業を待って次の処理を行う」といったイメージです。
~非同期処理とは~
原則的にコードは上から順に処理していきますが、「サーバとのデータのやりとりなど時間がかかる作業と並行して次の処理を行う」
※詳細なイメージは@don-bu-rakkoさんの以下をご参考にして頂けると良いかと思います。
https://qiita.com/don-bu-rakko/items/b283829c4572a6425a5c
→記事はこちらに移転したそうです
Promiseとasync/awaitを平たく言えば
Promise
は(オブジェクトとして)処理の状態を持たせて、処理の状態を変化させることで、引数で取る関数の処理を 非同期に進めたり、 待たせたりすることができます。
async
で宣言した関数はawait
(演算子)を使うことができます。await
を挟むと、Promise
に変換することができ、処理終了後resolved
の状態であるPromise
となります。resolved
になるまで関数内の他の処理が一時停止するので、 待つことができます。
Promiseについて
上記のような非同期処理であるJavaScriptにおいて、Promise
(正確にはPromiseオブジェクト
オブジェクトなんですね!)は処理の 「状態」 を表すものです。処理順序の決まり(=Promise)を指定するものって感じですね。
「状態」 は以下の3種類あります。(太字の英語は今後も出てくるかもしれないです)
Promise
の状態
- 待機 (pending): 初期状態。成功も失敗もしていません。
- 成功 (resolved): 処理が成功して完了したことを意味します。
- 失敗 (rejected): 処理が失敗したことを意味します。
└細かくは、 fulfilledや settledもありますが、一旦上記3点でことは足ります。
Promiseの使い方
基本的なPromiseの記述
Promiseは引数に即時関数をとります。中の関数の引数にはresoleve
関数とreject
関数をとります。
// 基本的な記述
const promise = new Promise(function(resolve,reject){
//ここに処理を書きます
});
// アロー関数での記述
const promise = new Promise((resolve, reject) => {
//ここに処理を書きます
});
まず、この時点での 「状態(=PromiseStatus
)」 は pending
です。(初期状態)
このPromiseStatus
を変化させるために使うものがresolve()
とreject()
です。
前者を呼び出すとPromiseStatus
は resolved
となり、後者を呼び出すと rejected
になります。
そしてPromiseStatus
が変化すると、resolved
だと.then
が呼び出され、rejected
だと.catch
が呼び出されます。
resolve()
とreject()
は引数を取ると、それぞれ.then
と.catch
の引数に使われます。
const promise = new Promise((resolve, reject) => {
resolve(variable1);
// reject(variable2);
})
.then((variable1) => {
console.log("resolveしました"); // こっちが実行される
})
.catch((variable2) => {
console.log("rejectしました");
});
const promise = new Promise((resolve, reject) => {
// resolve(variable1);
reject(variable2);
})
.then((variable1) => {
console.log("resolveしました");
})
.catch((variable2) => {
console.log("rejectしました"); // こっちが実行される(エラー実行のイメージです)
});
.then
と.catch
の連鎖
resolve
したか、reject
されたかで.then
と.catch
を連鎖させていくことができます。
変数の受け渡しはreturn
を使用することで利用可能です。
なお、.catch
が完了するとPromiseStatus
はresolved
になります。
(エラー後の処理を実行したことで、「エラーを解決した!」という理解)
そのため、.catch
の後に.then
を記述することで.catch
後も連鎖させていくことができます。
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
※コードは以下より引用
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises
Promiseの同時並行処理
複数のPromiseを実行する際に、以下のような処理を行いたいときに使用するメソッドがあります。
Promise
のメソッド
-
全てのPromiseが終了したら実行
Promise.all
-
いずれかのPromiseが終了したら実行
Promise.race
Promise.all([promise1, promise2]).then(() => {
console.log("promise1とpromise2が終わりました");
});
Promise.race([promise1, promise2]).then(() => {
console.log("promise1かpromise2が終わりました");
});
async/awaitとその使い方
async
は非同期関数を定義する関数宣言で返り値はPromise
オブジェクトとなります。
async
で宣言した関数を実行した際に返ってくるPromise
はresolved
となっているため、.then
で続けることができます。
async
で宣言した関数内でawait
演算子を使用するとその処理が終了するまで関数内の実行を一時停止することができます。
await
の配置次第で前述のPromise.all
やPromise.race
を実現することもできます。
const asyncFunc = async () => {
let x, y;
// Promiseがresolveするまで待機
x = await new Promise((resolve) => { // 2行下で返された1がawait演算子によってresolvedのPromiseとしてxに代入される
setTimeout(() => {
resolve(1); // 1000ms後にPromiseをresolvedにし、resolveの引数内の1を返す
}, 1000);
});
// Promiseがresolveするまで待機
y = await new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 1000);
});
console.log(x + y);
};
なお、await
に続く式がPromise
オブジェクトでない場合、resolved
となったPromise
に変換されます。
async function f1() {
var y = await 20;
console.log(y); // 20
}
f1();
※コードは以下の記事より引用しました。
https://qiita.com/cheez921/items/41b744e4e002b966391a#await%E3%81%A8%E3%81%AF
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await
Promise
オブジェクトになってしまったときの対応法
ところで、await
を使うと、上記のようにPromise
で返ってくることがあります。Promise
オブジェクトの中身を取ってくる方法が以下です。
ここではPromise
になってしまったtmp
の値を取ってみます。
// Promiseオブジェクトになってしまったtmp
tmp.then( function(value) {
console.log(value) //valueがtmpの中身の値です
})
参考文献