#JavaScriptの非同期処理についてまとめてみた
##非同期処理とは?
まず、普通のプログラムを考えます
通常のプログラムだと
console.log("aiueo");
console.log("kakikukeko");
aiueo
kakikukeko
このように上から順にプログラムが実行されていきます。
これを同期的な処理と言います
では次のようなプログラムはどうでしょうか?
setTimeout(
() => {
console.log("aiueo");
}, 5 * 1000
)
console.log("kakikukeko");
kakikukeko
aiueo
この場合は5秒後に最初のconsole.logが実行されます。
このようにあるタスク(setTimeoutの処理)が実行されているときに他のタスク(console.log("kakikukeko"))が実行されるような処理を非同期処理と言います。
##JavaScriptの非同期処理
JavaScriptの非同期処理の方法はいくつかあり、大まかにcallback時代,ジェネレーター時代,Promise時代async,await時代に分けられます、
この記事では全ては紹介しませんが、callbackとpromiseを重点的にまとめていきたいと思います。
##callback時代
callbackは先ほど紹介したサンプルのようにsetTimeoutやsetIntervalなどの関数を使って非同期処理をすることを言います。
###スコープ
非同期の処理をする際、スコープに注意する必要があります。
例えば
function countdown() {
let i;
console.log("count down");
for(i = 5; i >= 0; i--) {
setTimeout(
() => {
console.log(i === 0 ? "GO!" : i);
},(5 - i) * 1000
)
}
}
countdown();
このような処理を考えて見ましょう
この場合は-1が6回出力されて終了します。forループが実行され値が-1となるのです。
そしてその後にcallbackが実行されます。
これはcountdownが実行されるときに変数iを含むクロージャーが生成されますforループ内では非同期的に全て同じ変数iにアクセスしています。
しかし、この関数内の遅延時間の計算の部分は同期的に行われる((5-i)*1000の部分)ので5秒間のカウントダウンは正常に行われるのです。
setTimeoutの呼び出し自体も同期的に行われています。
##Promise時代
###Promiseの生成
Promiseの生成はPromiseの引数に非同期処理の関数を渡して、インスタンスを生成するだけです
new Promise(<Some Ajax Function>);
上の<Some Ajax Function>には二つの引数を指定します。
new Promise(function(onFulfilled, onRejected) {
//* some ajax process *//
})
上記のような感じで指定します。
onFulfilledは処理が成功したときの処理、
onRejectedは処理が失敗したときの処理です。
最初に提示したcountdownの例をPromiseベースに変えて見ます.
function countdown(seconds) {
new Promise((onSuccess, onFailed) => {
for(let i = 0; i <= seconds; i++) {
setTimeout(() => {
console.log(`${i}秒経過`);
}, i * 1000)
}
});
}
このようにPromiseを生成します。
###Promiseの利用
上記の例ではcountdown();を実行できますが、それでは戻されるPromiseを利用していないので意味がありません
では、Promiseを利用する処理を考えてみましょう。
function countdown(seconds) {
return new Promise((onSuccess, onFailed) => {
for(let i = 0; i <= seconds; i++) {
setTimeout(() => {
console.log(`${i}秒経過`);
if(i === 5)
onSuccess();
}, i * 1000)
}
});
}
countdown(5).then(
() => { //*成功した時onSuccessの処理
console.log("カウントダウン成功!");
},
(err) => { //*失敗した時onFailedの処理
console.log(err);
}
)
関数countdownを実行するとPromiseが返され、Promiseのメソッドthenが実行されます
そこでPromiseの中の処理が成功したらthenの第一引数の関数がonSuccessに渡され、失敗したら第二引数の関数がonFailedの渡されるのです。
その他にPromiseには失敗した時用にcatchメソッドが用意されており、これを使うことによって以下のように表現することもできます。
const p = countdown(5);
p.then(() => {
console.log("カウントダウン成功");
}).catch((err) => {
console.log("err");
});
エラーを出したい時は以下のようにします。
function countdown(seconds) {
return new Promise((onSuccess, onFailed) => {
for(let i = 0; i <= seconds; i++) {
setTimeout(() => {
console.log(`${i}秒経過`);
if(i === seconds)
onSuccess();
if(i === 3)
return onFailed(new Error("数字の3はカウントできません"));
}, i * 1000)
}
});
}
const p = countdown(5);
p.then(() => {
console.log("カウントダウン成功");
}).catch((err) => {
console.log("err");
});
Promiseのチェイニング
Promiseは.thenを繋げてチェイニングできます。
例
function countdown(seconds) {
return new Promise((onSuccess, onFailed) => {
const timeoutIds = [];
for(let i = 0; i <= seconds; i++) {
timeoutIds.push(setTimeout(() => {
if(i === 13) {
timeoutIds.forEach(clearTimeout); //カウントダウン取り消し
return onFailed(new Error("13は不吉な数字です"));
} else if (i === 15) {
onSuccess(console.log("カウントダウン成功"));
}
console.log(`${i}秒経過`);
}, i * 1000));
}
});
}
function launch() {
return new Promise((onSuccess, onFailed) => {
console.log("発射!");
setTimeout(() => {
onSuccess("周回軌道に乗った!!"); //次のthenの引数msgに渡される
}, 2 * 1000);
});
}
countdown(15)
.then(launch)
.then((msg) => {
console.log(msg);
})
.catch((err) => {
console.log(err);
});
上記例のようにチェイニングできます。
##async, await
関数の前にasyncを付けることでasync functionにすることができます。
演算子awaitはasync function内で使うものでPromiseが成功するまで待ちます。
例
const fs = require('fs');
function readF(filename) {
return new Promise((onSuccess, onFailed) => {
fs.readFile(filename, "utf-8", (err, data) => {
err ? onFailed(err) : onSuccess(data);
});
});
}
function writeF(filename, data) {
return new Promise((onSuccess, onFailed) => {
fs.writeFile(filename, data, err => {
err ? onFailed(err) : onSuccess('OK');
});
});
}
async function readAndWrite() {
try {
let writeData = await readF("a.txt"); // a.txtを読み込んでwriteDataへ代入
writeData += await readF("b.txt"); // b.txtを読み込んでwriteDataへ追加
await writeF("d.txt", writeData); // d.txtへ書き込み
} catch (err) {
console.error(err);
}
}
readAndWrite();
a
b
結果
a
b
非同期処理を行うreadF,writeF関数それぞれでawaitを使って、Promiseがresolveするまで待っています。
try catchを使ってエラー処理をすることもできます。
まとめ
この記事ではJavaScriptの非同期処理のうち、callbackとpromiseについて扱ってきました。
ミスがあったらご指摘お願いしますm(__)m