##非同期処理の動作を確認
setTimeout関数で簡単な非同期な関数を確認することができます。
// setTimeout:第一引数の関数を、第二引数のミリ秒後に実行
// 2秒後に'Hello'を出力
const outputHello = () => {
setTimeout(() => {
console.log('Hello');
}, 2000);
}
// 1秒後に'World'を出力
const outputWorld = () => {
setTimeout(() => {
console.log('World!');
}, 1000);
}
outputHello();
outputWorld();
##実行結果
前ページのコードをブラウザのコンソールに貼り付けて実行します。
出力結果は期待通りだったでしょうか?
先に書いたsetTimeoutのコールバック関数の実行より前に、後ろで書いたsetTimeoutのコールバック関数が実行されています。
##非同期処理制御の必要
非同期処理が絡むと、単純に順番に書いただけでは、想定する順番で動かないことがあります。
例えば、サーバAPIでJSONデータをフェッチした後に、ローカル変数に代入する場合は、レスポンスが返ってくる前に、代入の処理に進んでしまいます。
##コールバック(非推奨)
非同期処理の古典的な実装にコールバックを段々と積み上げることで順序保障する方法があります。
例外処理などを書いていくと、コード見通しが非常に悪くなるので、現在ほぼ使う機会はないかと思います。
// 2秒後に'Hello'を出力
const outputHello = (callback) => {
setTimeout(() => {
console.log('Hello');
callback(); // コールバック関数を実行
}, 2000);
}
// 1秒後に'World'を出力
const outputWorld = () => {
setTimeout(() => {
console.log('World!');
}, 1000);
}
outputHello(outputWorld); // outputWorld関数をoutputHello内で実行する
##Promise
JavaScript標準でpromiseという非同期処理を制御するためのものが用意されています。
後述するasync/awaitなどもフロントエンドの非同期処理の基盤となっている機能です。
##Promiseに書き換え
さきほどのsetTimeoutの関数をPromiseで扱えるようにします。
API通信時はaxiosなどライブラリを使うことが多いので、実業務でPromiseを返す関数を作成することは少ないかと思います。
// 2秒後に'Hello'を出力
const outputHello = () => {
return new Promise(function(resolve, reject) {
setTimeout(() => {
// 乱数(0〜10)が3未満の場合はreject
Math.random() *10 > 3
? resolve(console.log('Hello'))
: reject(Error('outputHelloでエラー発生'));
}, 2000);
});
}
// 1秒後に'World'を出力
const outputWorld = () => {
return new Promise(function(resolve, reject) {
setTimeout(() => {
// 乱数(0〜10)が3未満の場合はreject
Math.random() *10 > 3
? resolve(console.log('World!'))
: reject(Error('outputWorldでエラー発生'));
}, 1000);
});
}
outputHello()
.then(outputWorld)
.catch((e) => {
console.log(e.message);
})
.finally(()=>{
console.log('処理終了');
});
##Promiseの状態
Promiseを返却する関数は3つ状態を持ちます。
- pending
resolveもrejectも実行していない状態。例えば。APIサーバに投げたりクエストが返ってきていない状態など。
- fulfill
resolveが実行されて、正常に処理が完了した状態。thenでチェインしている場合、次の関数に進む。
- reject
rejectが実行された状態。基本的に例外が発生した場合にrejectとなる。thenでチェインしている場合でも、次の関数に進まず、catchメソッドに飛ぶ。
##便利メソッド(Promise.all)
複数のPromiseを返す関数を一斉に走らせて、全ての関数のPromiseの状態がfullfillになるとthenを実行する。
それぞれの非同期処理の順序はどうでも良いが、全て完了したあとに処理を行いたい際に使う。
※1つでもrejectがあると、catchを実行する。
Promise.all([outputHello(), outputWorld()])
.then(()=> {
// all fulfill
})
.catch((e)=> {
// exist reject
});
たいていの処理がPromise.thenとPromise.allで対応できますが、必要に応じて他のメソッドも検討します。
##async/await
Promiseは非同期処理をコールバックを使わずに記述できますが、async/awaitを使うことで、さらに簡略化できます。
awaitの役割
awaitを付けるとPromiseの結果が返却されるまで処理を待機します。
thenでチェインする必要なく記述した順序で実行され、Promiseの結果を待ちます。
後述するasyncを付けた関数内でしか使用できません。try-catchで例外を捕捉することができます。
try {
await outputHello();
await outputWorld();
} catch (e) {
console.log(e.message)
} finally {
console.log('処理終了')
}
asyncの役割
- asyncを付けた関数(async function)がPromiseを返すようになる。
- async function内でreturnすると、resolveを実行する
- async function内でthrowすると、rejectを実行する
要するに非同期処理をあまり意識せずに、returnしたら正常終了、throwしたら例外発生とすることができる。
asyncのサンプルコード
// 2秒後に'Hello'を出力
const outputHello = () => {
return new Promise(function(resolve, reject) {
setTimeout(() => {
// 乱数(0〜10)が3未満の場合はreject
Math.random() *10 > 3
? resolve(console.log('Hello'))
: reject(Error('outputHelloでエラー発生'));
}, 2000);
});
}
// 1秒後に'World'を出力
const outputWorld = () => {
return new Promise(function(resolve, reject) {
setTimeout(() => {
// 乱数(0〜10)が3未満の場合はreject
Math.random() *10 > 3
? resolve(console.log('World!'))
: reject(Error('outputWorldでエラー発生'));
}, 1000);
});
}
const sequence = async () => {
try {
await outputHello();
await outputWorld();
} catch (e) {
throw e;
} finally {
console.log('sequence処理終了')
}
}
// asyncをつけるとPromiseを返す関数となるので、Promiseのメソッドが使える!
sequence()
.then(sequence)
.catch((e)=> console.log(e.message))
.finally( ()=> console.log('処理終了'));
##おまけ
##課題1
コンソール出力内容は?
setTimeout(function() {
console.log('A');
}, 5000);
setTimeout(function() {
console.log('B');
}, 4000);
setTimeout(function() {
console.log('C');
}, 3000);
setTimeout(function() {
console.log('D');
}, 2000);
setTimeout(function() {
console.log('E');
}, 1000);
##課題2
コンソール出力内容は?
setTimeout(function() {
console.log('A');
setTimeout(function() {
console.log('B');
setTimeout(function() {
console.log('C');
setTimeout(function() {
console.log('D');
setTimeout(function() {
console.log('E');
}, 1000);
}, 2000);
}, 3000);
}, 4000);
}, 5000);
##課題3
コンソールにHelloと出力されましたが、続いてWorldが出力されません。
const outputHello = () => {
return new Promise(function(resolve, reject) {
setTimeout(() => {
console.log('Hello');
}, 2000);
});
}
const outputWorld = () => {
return new Promise(function(resolve, reject) {
setTimeout(() => {
console.log('World!');
}, 1000);
});
}
outputHello()
.then(outputWorld)
.catch((e) => {
console.log(e.message);
})
.finally(()=>{
console.log('処理終了');
});
##課題4
asyncの機能の認識を間違って実装している部分があります。指摘してください。
// httpリクエストを発行する関数
public async executeRequest ():Promise<any> {
if (!this.isValid) {
return new Promise(() => {
throw new InvalidError('無効なリクエストです。');
});
}
// axiosを使ってJSONデータをリクエスト
return axios.request(this.config)
.then((res) => {
return res.data;
})
.catch((e) => {
throw new Error(e.message);
});