javaScript非同期処理まとめ
javaScript 非同期処理まとめ
javaScript の非同期処理は、callback, promise, await-async を使用した
三種類の方法がある。それぞれ確認していこう。
callback
callback 関数を使用することで非同期処理を実現できます
しかし、モダン javaScript ではあまり使わないのでさらっと流します
では試しに setTimeout で 1 秒後に処理を行う wait 関数を定義しましょう
num を表示した後に callback 関数を実行する関数になっています
const wait = (callback, num) => {
setTimeout(() => {
console.log(num);
callback(num);
}, 1000);
};
//実行
wait((num) => {
console.log("test");
}, 0);
実行結果は以下になります。0 が表示された後に test と表示されていますね。
ここから、wait の実行が終わったら違う処理をしたい場合を考えましょう
今回は wait を入れ子にすることで実現します
wait((num) => {
num++;
wait((num) => {
num++;
wait((num) => {
num++;
console.log("test");
}, num);
}, num);
}, 0);
実行結果
1 秒ごとに0,1,2,と表示の後 test と表示する
これで非同期処理が実現できてる!やったー!
…いや、入れ子構造は見づらいわ!!!
というわけでこのめんどくささの解決をしましょう
Promise
先ほどの wait を Promise を使って書き換えます
const wait = (num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(num);
resolve(num);
}, 1000);
});
};
最初見た時「なんだこれ」と思いましたが、めちゃくちゃよく出来てるメソッドですよね。
ここで return で Promise クラスを返しているのはわかると思います。
問題は引数の resolve と reject です。
とりあえず resolve を見ていきましょう。
resolve とは?
Promise はメソッドチェーンでつなげることが出来るのですが、
その戻り値が resolve で設定した値になります
そしてメソッドチェーンでつなげる方法は、then メソッドでつないでいきます
wait(0).then((num) => {
num++;
// returnでPromiseを返すとチェーンが継続し非同期処理ができる
return wait(num);
})
上記のようにすることで、wait(0)の実行後に then メソッド以降の処理を行えます
さらに、return で Promise を返すことで、さらにメソッドチェーンをつなげることができます
(この場合、wait の戻り値は Promise になっている)
では return で Promise を戻さなかった場合は?
もちろん非同期処理が崩れる
wait(0).then((num) => {
num++;
return wait(num);
})
.then((num) => {
num++;
// 戻り値をPromiseにしない
wait(num);
// チェーンが切れて非同期処理が出来なくなる
return num;
})
.then((num) => {
num++;
//2と3は同時に出力される
wait(num);
return num;
});
2つめの then メソッドの中で、return で Promise を戻さなかったため、
2,3 は同時に出力されるようになる
reject は?
reject は何に使うかというと、エラーが起きた時の処理に使用します
const wait = (num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(num);
// numが2になったらエラーとする
if (num === 2) {
reject(num);
} else {
resolve(num);
}
}, 1000);
});
};
エラーになったら処理はどうなるのか?
他の言語の場合、例えば Java ならエラーは try して catch しますよね?
この場合も同じで、reject に値が渡った時点で then メソッドではなく
catch メソッドに処理が渡ります
wait(0)
.then((num) => {
num++;
return wait(num);
})
.then((num) => {
num++;
return wait(num);
})
.catch(num => {
// rejectに値が渡ったらcatchに渡り、
// 引数がrejectに設定した値になる
// 今回の場合はnum
num++;
console.error(num, 'error');
})
実行結果
並列処理
Promise を使って、複数のメソッドを並列に処理し、全てが終わったら次の工程に進むということができます
まずは wait を引数に与えられた秒数待つように変更
const wait = (num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(num);
resolve(num);
}, num);
});
};
そして並列処理は以下のように Promise.all メソッドを使います
Promise.all([wait(1000), wait(1500), wait(2000)]).then((nums) => {
console.log(nums);
});
実行結果
全ての wait が終わった後に console.log で戻り値を表示していることがわかります
そして、戻り値は配列で渡されていることがわかります
もう一つ並列処理があります。
一つでも処理が終われば次の処理に進む race というメソッドです
Promise.race([wait(1000), wait(1500), wait(2000)]).then((nums) => {
console.log(nums + 1);
});
実行結果
凄く簡単に非同期処理が実現できますね!
「でもさー、もうちょっと直感的に書けない?メソッドチェーンムズくない?」
という方は、await と async を考えてみましょう
await と async
- await ... Promise を返すメソッドにつけることで、非同期処理を行える
- async ... await を持つメソッドの頭につけることで、非同期処理であることを教える
これらを使うと、先ほどの wait メソッドを、then を使わずに記述することができます
const wait = (num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(num);
resolve(num);
}, 1000);
});
};
const init = async () => {
let num = await wait(0);
num++;
num = await wait(num);
num++;
num = await wait(num);
num++;
return num;
};
init();
実行結果
なるほど、記述した順番通りに処理が実行されていますね。
「そしたら、reject を使ってエラー処理を行う場合はどうなるのさ?」
その場合は、try-catch 文を使って実現することができます。よく見る形ですね。
const wait = (num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(num);
if (num === 2) {
reject(num);
} else {
resolve(num);
}
}, 1000);
});
};
const init = async () => {
let num;
try {
num = await wait(0);
num++;
num = await wait(num);
num++;
num = await wait(num);
num++;
} catch (error) {
console.error(error, "error");
}
return num;
};
init();
実行結果
ちなみに戻り値は Promise でラップされたものになるので注意しましょう
そのため、init メソッドも以下のように書くことができますね。
init().then((num) => {
console.log(num, "End");
});
参考
Discussion