Edited at

JavaScriptの非同期処理について

More than 1 year has passed since last update.


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.txt

a



b.txt

b


結果


d.txt

a

b

非同期処理を行うreadF,writeF関数それぞれでawaitを使って、Promiseがresolveするまで待っています。

try catchを使ってエラー処理をすることもできます。


まとめ

この記事ではJavaScriptの非同期処理のうち、callbackとpromiseについて扱ってきました。

ミスがあったらご指摘お願いしますm(__)m

参考 初めてのJavaScript