async/awaitについて理解する
async/awaitはPromiseをより直感的に記述できるようにしたもの(ES8)
asyncと書けば必ずPromiseが返ってくることが担保されている。
awaitはPromiseを返却する関数の非同期処理が完了するまで待って次の処理に移行するようにする。
Promiseの処理をasync/awaitに書き換える
Promiseの処理
function sleep(val) { return new Promise((resolve, reject) => { setTimeout(() => { console.log(val++); resolve(val); }, 1000); }); } sleep(0) .then((val) => { return sleep(val); }) .then((val) => { return sleep(val); }) .then((val) => { return sleep(val); }) .then((val) => { return sleep(val); }) .then((val) => { return sleep(val); }); console.log("global end"); // Promise // global end // 0 // 1 // 2 // 3 // 4 // 5
上記のPromise処理をasync/awaitに置き換える
function sleep(val) { return new Promise((resolve, reject) => { setTimeout(() => { console.log(val++); resolve(val); }, 1000); }); } async function init() { let val = await sleep(0); val = await sleep(val); val = await sleep(val); val = await sleep(val); val = await sleep(val); val = await sleep(val); } init(); console.log("global end"); // 結果は同上
awaitを使用すると、返り値としてresolveが返ってくる。 awaitを付けないと、Promiseオブジェクトが返ってくる(returnされているので)
async自体もPromsieのインスタンスを返す。(console.log(init())
で確認できる)
なので、init()
にも.then
を繋げることができ、asyncの処理が終了したら.thenの中が実行する。
また、async内でreturn val
をすると、.then(val)
に値を引き渡すことができる。
// 省略 async function init() { let val = await sleep(0); val = await sleep(val); val = await sleep(val); val = await sleep(val); val = await sleep(val); val = await sleep(val); return val } init().then((val) => { console.log("hello"); });
参考
MicroTasksとMacroTasksの実行順番について
MicroTasksとMacroTasksについて
- MicroTasksの処理が全て完了したら、MacroTasksの実行が開始される(MicroTasks優先)
MicroTasksの特徴
順番が回ってきたら全てのジョブを実行
Promises
、queueMicrotask
、MutationObserver
など
MacroTasksの特徴
順番が回ってきたら一つずつタスクを実行
setTimeout
、setInterval
、requestAnimationFrame
など
▼サンプルコード
setTimeout(() => { console.log("MACRO task1"); }); new Promise((resolve, reject) => { console.log("Promise"); resolve(); }).then(() => { console.log("micro task1"); }); console.log("global end"); // Promise // global end // micro task1 // MACRO task1
Promiseの中にsetTimeoutを入れたときの挙動
new Promise((resolve, reject) => { console.log("Promise"); setTimeout(() => { console.log("MACRO task1"); resolve(); }); }).then(() => { console.log("micro task1"); }); console.log("global end"); // Promise // global end // MACRO task1 // micro task1
thenの中にsetTimeoutを入れたときの挙動
new Promise((resolve, reject) => { console.log("Promise"); setTimeout(() => { console.log("MACRO task1"); resolve(); }); }) .then(() => { console.log("micro task1"); }) .then(() => { console.log("micro task2"); setTimeout(() => { console.log("MACRO task2"); }); }) .then(() => { console.log("micro task3"); }); console.log("global end"); // Promise // global end // MACRO task1 // micro task1 // micro task2 // micro task3 // MACRO task2
参考
非同期処理のPromiseを理解する
Promiseとは
コールバック地獄を解消し、非同期処理をより簡単に可読性が上がるように書けるようにしたもの。
基本的なPromise構文
new Promise((resolve, reject) => { console.log("promise"); // 同期処理 resolve("resolve data"); // or reject('reject data'); }) .then(() => { console.log("then"); // 非同期処理 }) .catch(() => { console.log("catch"); // 非同期処理 }) .finally(() => { console.log("finally"); // 非同期処理 }); console.log("global end"); // promise // global end // then(rejectであれば、「catch」が返ってくる) // finally
Promiseの挙動
下記のように、resolve(); や reject(); を使用しなければ、非同期処理(.then(), .catch(), .finally())は走らない
new Promise((resolve, reject) => { console.log("promise"); }) .then(() => { console.log("then"); }) .catch(() => { console.log("catch"); }) .finally(() => { console.log("finally"); }); console.log("global end"); // promise // global end
データの受け渡し
resolve
なら.then()の引数に、reject
なら.catch()の引数にそれぞれデータが渡る。
ただし、.finallyにはデータは渡らずundefinedになる。
new Promise((resolve, reject) => { console.log("promise"); resolve("resolve data"); // or reject("reject data") }) .then((data) => { console.log("then: " + data); }) .catch((data) => { console.log("catch: " + data); }) .finally((data) => { console.log("finally: " + data); }); console.log("global end"); // promise // global end // then: resolve data(rejectであれば、「then: reject data」が返ってくる) // finally: undefined
Promiseの非同期処理の実行
new Promise((resolve, reject) => { console.log("promise"); // setTimeoutを利用し、2秒後にresolve()を実行 setTimeout(() => { resolve("resolve data"); // or reject("reject data") }, 2000); }) .then((data) => { console.log("then: " + data); }) .catch((data) => { console.log("catch: " + data); }) .finally((data) => { console.log("finally"); }); console.log("global end"); // promise // global end // (...2秒後) // then: resolve data // finally
callback地獄をPromsieに書き換える
callback関数
function sleep(callback, val) { setTimeout(() => { console.log(val++); callback(val); }, 1000); } sleep((val) => { sleep((val) => { sleep((val) => { console.log("callback hell end"); }, val); }, val); }, 0); console.log("global end"); // global end // 0 // 1 // 2 // callback hell end
Promise構文
function sleep(val) { return new Promise((resolve, reject) => { setTimeout(() => { console.log(val++); resolve(val); }, 1000); }); } sleep(0) .then((val) => { return sleep(val); }) .then((val) => { return sleep(val); }); console.log("global end"); // global end // 0 // 1 // 2
全てのPromise処理が終わった後に処理を実行する(Promise.all)
sleep(0), sleep(1), sleep(2) の処理全てが完了した後に、thenが実行
function sleep(val) { return new Promise((resolve, reject) => { setTimeout(() => { console.log(val++); resolve(val); }, val * 1000); }); } Promise.all([sleep(0), sleep(1), sleep(2)]).then((val) => { console.log(val); }); console.log("global end"); // global end // 0 // 1 // 2 // [ 1, 2, 3 ]
参考
非同期処理のcallback関数を理解する
callback関数とは
callback関数は、ある関数(A関数)が実行される際に、その中で別の関数(B関数)を呼び出すこと。
console.log("start"); function timeout() { // B関数 console.log("setTimeout"); } setTimeout(timeout, 1000); // A関数 console.log("end"); // コンソール結果 // start // end // setTimeout
または下記のような記述もできる。
console.log("start"); setTimeout(() => { console.log("setTimeout"); }, 1000); console.log("end"); // コンソール結果は同上
callback関数の欠点
【欠点1】callback地獄
可読性が著しく低くなる
function sleep(callback, val) { setTimeout(function () { console.log(val++); callback(val); }, 1000); } sleep(function (val) { sleep(function (val) { sleep(function (val) { sleep(function (val) { sleep(function (val) { sleep(function (val) { console.log("callback hell done"); }, val); }, val); }, val); }, val); }, val); }, 0);
【欠点2】エラー処理がうまく動かない
下記コードは、try文の中でsetTimeoutが実行されない(1秒後に実行)。
// 正常に例外処理が実行されないコード try { setTimeout(() => { throw new Error("setTimeout error"); }, 1000); } catch (error) { console.log(`catch error: ${error}`); }
下記のようにsetTimeoutの中に例外処理を書けば正常に動く
// 正常に例外処理が実行されるコード setTimeout(() => { try { throw new Error("setTimeout error"); } catch (error) { console.log(`catch error: ${error}`); } }, 1000);
上記のような課題を解決するために、Promise
オブジェクトが誕生。