はじめに
最近「フロント作業して。Vue.js書いて」って言われて、久々に「非同期処理を逐次処理」したいということで色々調べているときに
少し脱線して、Javascriptで「”非同期処理を逐次処理”の知識忘れているな・・・よし整理しよ」するために調べた内容をメモ。
非同期処理を逐次処理するために、Promiseを使って解決はできるのですが、PromiseはES2015の時の技術であり、以下の点で問題がありました。
複雑な処理になると、
- 可読性の低いソースコードになる。
- コールバック地獄を解消するためのものなのに、コールバック地獄のようにネストが深くなる。
ここで、これらの問題を解決するために2017年6月にリリースされたES2017、async/awaitが効果がありますと。
また、ES2015から機能や構文が追加されて、おーってなっていたので、こちらを参考にソースコードの書き方も前回の記事とは変えてみています。
結論
以下ご指摘いただいた内容でのソースです。
めちゃくちゃすっきりしたヤーン。
@shiracamus さんありがとうございます!
ご指摘いただいたasync/awaitの処理
const promise = obj => new Promise(resolve => setTimeout(() => resolve(obj > 5 ? "Power" : "Not Power"), 1000))
const firstStep = async (random) => {
console.log(await promise(random));
console.log(await promise(Random()));
console.log(await promise(Random()));
}
// ランダムな数を生成
const Random = () => {
var random = Math.random () * 10;
console.log(random);
return random;
}
firstStep(Random())
async/awaitの処理
const promise = (obj) => new Promise(
(resolve, reject) => {
setTimeout(
() => {
if(obj > 5){
const message = "Power";
// console.log("success");
// 呼び出し先にresolveを返す
resolve(message);
}
else{
const message = "Not Power";
// console.log("error");
// 呼び出し先にrejectを返す
reject(message);
}
}, 1000)
});
const firstStep = async (random) => {
const res01 = await promise(random)
// promiseから受け取ったresolveを処理
.then(
message => {
console.log(message)
})
// promiseから受け取ったrejectを処理
.catch(
message => {
console.log(message)
});
const res02 = await promise(Random())
// promiseから受け取ったresolveを処理
.then(
message => {
console.log(message)
})
// promiseから受け取ったrejectを処理
.catch(
message => {
console.log(message)
});
const res03 = await promise(Random())
// promiseから受け取ったresolveを処理
.then(
message => {
console.log(message)
})
// promiseから受け取ったrejectを処理
.catch(
message => {
console.log(message)
});
}
// ランダムな数を生成
const Random = () => {
var random = Math.random () * 10;
console.log(random);
return random;
}
firstStep(Random());
内容
async/awaitを使わない処理の場合とasync/awaitを使う時の処理で比較して、どれだけ簡潔になったかを見ていただきたいと思います。
例が少し複雑かもしれません。。すみません。
async/awaitを使わない処理の場合だと
promiseのみの処理
var promise = (obj) => new Promise(
(resolve, reject) => {
setTimeout(
() => {
if(obj > 5){
const message = "Power";
// 呼び出し先にresolveを返す
resolve(message);
}
else{
const message = "Not Power";
// 呼び出し先にrejectを返す
reject(message);
}
}, 1000);
});
// ランダムな数を生成
const Random = () => {
var random = Math.random () * 10;
console.log(random);
return random;
}
// promiseの実行
promise(Random())
// promiseから受け取ったresolveを処理
.then(
(firstRes) => {
const message = firstRes;
console.log(message);
// promiseの実行
promise(Random())
// promiseから受け取ったresolveを処理
.then(
(secondRes) => {
const message = secondRes;
console.log(message);
// promiseの実行
promise(Random())
// promiseから受け取ったresolveを処理
.then(
(thirdRes) => {
const message = thirdRes;
console.log(message);
})
// promiseから受け取ったrejectを処理
.catch(
(thirdRes) => {
const message = thirdRes;
console.log(message);
});
})
// promiseから受け取ったrejectを処理
.catch(
(secondRes) => {
const message = secondRes;
console.log(message);
// promiseの実行
promise(Random())
// promiseから受け取ったresolveを処理
.then(
(thirdRes) => {
const message = thirdRes;
console.log(message);
})
// promiseから受け取ったrejectを処理
.catch(
(thirdRes) => {
const message = thirdRes;
console.log(message);
});
});
})
// promiseから受け取ったrejectを処理
.catch(
(errorFirstRes) => {
const message = errorFirstRes;
console.log(message);
// promiseの実行
promise(Random())
// promiseから受け取ったresolveを処理
.then(
errorSecondRes => {
const message = errorSecondRes;
console.log(message);
// promiseの実行
promise(Random())
// promiseから受け取ったresolveを処理
.then(
errorThirdRes => {
const message = errorThirdRes;
console.log(message);
})
// promiseから受け取ったrejectを処理
.catch(
errorThirdRes => {
const message = errorThirdRes;
console.log(message);
});
})
// promiseから受け取ったrejectを処理
.catch(
errorSecondRes => {
const message = errorSecondRes;
console.log(message);
// promiseの実行
promise(Random())
// promiseから受け取ったresolveを処理
.then(
errorThirdRes => {
const message = errorThirdRes;
console.log(message);
})
// promiseから受け取ったrejectを処理
.catch(
errorThirdRes => {
const message = errorThirdRes;
console.log(message);
});
});
});
このように、読む気すら起きないほどネストが深くなってしまい、ほぼコールバック地獄と変わらない。。となってしまいます。
しかし、async/awaitを使う時の処理だと(結論のところでも書いていますがもう一度)
async/awaitの処理
ご指摘いただいた後の処理
const promise = obj => new Promise(resolve => setTimeout(() => resolve(obj > 5 ? "Power" : "Not Power"), 1000))
const firstStep = async (random) => {
console.log(await promise(random));
console.log(await promise(Random()));
console.log(await promise(Random()));
}
// ランダムな数を生成
const Random = () => {
var random = Math.random () * 10;
console.log(random);
return random;
}
firstStep(Random())
ご指摘いただく前の処理
const promise = (obj) => new Promise(
(resolve, reject) => {
setTimeout(
() => {
if(obj > 5){
const message = "Power";
// console.log("success");
// 呼び出し先にresolveを返す
resolve(message);
}
else{
const message = "Not Power";
// console.log("error");
// 呼び出し先にrejectを返す
reject(message);
}
}, 1000)
});
const firstStep = async (random) => {
const res01 = await promise(random)
// promiseから受け取ったresolveを処理
.then(
message => {
console.log(message)
})
// promiseから受け取ったrejectを処理
.catch(
message => {
console.log(message)
});
const res02 = await promise(Random())
// promiseから受け取ったresolveを処理
.then(
message => {
console.log(message)
})
// promiseから受け取ったrejectを処理
.catch(
message => {
console.log(message)
});
const res03 = await promise(Random())
// promiseから受け取ったresolveを処理
.then(
message => {
console.log(message)
})
// promiseから受け取ったrejectを処理
.catch(
message => {
console.log(message)
});
}
// ランダムな数を生成
const Random = () => {
var random = Math.random () * 10;
console.log(random);
return random;
}
firstStep(Random());
このように、すっきりと可読性の高いソースになったことがわかると思います。(少し例が悪すぎたかもしません。お許しを。)
promiseから受け取る部分だけを見ると、ソースコードの行数が
- async/awaitを使う時の処理(ご指摘いただく前) : 約40行
- async/awaitを使う時の処理(ご指摘いただいた後) : 約13行
- async/awaitを使わない時の処理: 約120行
と、1/3もすっきりしています。
約1/10もすっきりしています。
さらに、ネストの深さも改善されていることがわかると思います。
ちょっとした説明
Javascriptは、同期的処理を管理するためにコールバック地獄が発生していました。その解決方法として、ES2015のPromiseが登場しました。ES2017ではさらに進化しています。
今回触れたのが、async/awaitでした。
再度になりますが、async/awaitは以下の利点があります。
- 可読性の高いソースコードになる
- 複雑な処理をしようとした場合Promiseよりもネストが深くならない
async/awaitを使わない時の処理よりも、直感的で書きやすいですよね。
jQueryのDeferredよりも書きやすく、わかりやすいのでお勧めです。
そもそもの話
async/awaitがどのブラウザに対応しているのかを報告します。
ただし、非対応のブラウザに関しては、Babel等々のトランスパイラを利用して、ES2015で書いたコードを各ブラウザで動くコードであるES5のコードにトランスパイル(変換)する必要があるそうです。
ECMAScript 2016+ compatibility tableで対応表を見ることができます。
async/awaitとは?というのは
今回、async/awaitを使用しないで短く非同期処理を逐次処理するための方法をメモしただけなので、詳しく知りたいという方はぜひ。
最後に
結論を見ていただいて・・・
非同期処理を逐次処理するために、async/awaitを使用しない場合と使用する場合で例を交えてお話しました。
**非同期処理を、可読性が高く、ネストも深くならず、逐次処理したい!**という方はぜひご参考にしていただければ幸いです。
参考
- async/awaitで非同期処理はシンプルになる
https://qiita.com/ryosukes/items/db8b45c8ea42f924f02f - ES2015(ES6) 入門
https://qiita.com/soarflat/items/b251caf9cb59b72beb9b - async/await 入門(JavaScript)
https://qiita.com/soarflat/items/1a9613e023200bbebcb3 - async / await の基本事項 ― やっぱりPromiseは無視できない
https://takamints.hatenablog.jp/entry/async-await-and-promise