リファレンスサイト
同期処理と非同期処理
- 同期処理
実行した順番に処理が実行される。 - 非同期処理
実行した順番に処理が実行されない。
同期処理
function func(i = null){
console.log('同期処理'+ ` ${i}` + 'です。');
}
func(1);
func(2); //上の結果をまってから実行される
func(3); //上の結果をまってから実行される
結果
同期処理 1です。
同期処理 2です。
同期処理 3です。
非同期処理
const asyncFunc = (time) => {
setTimeout(() => {
console.log(`${time}秒です。`);
}, time * 1000);
};
func(1);
asyncFunc(3);
func(2);
asyncFunc(1);
func(3);
結果 同期処理の後、非同期処理が実行される
同期処理 1です。
同期処理 2です。//asyncFunc(3);の実行結果を待たず
同期処理 3です。//asyncFunc(3);asyncFunc(1);の実行結果を待たず
1秒です。//asyncFunc(3);の実行結果を待たずにasyncFunc(1);がCB実行されている。
3秒です。
コールバック関数
高階関数とコールバック関数
function 高階関数(コールバック関数){
コールバック関数()
}
- コールバック関数の必要条件
関数の引数であること
実行できる値であること - 特徴
コールバック関数は高階関数の中で引数が渡されて実行される。
// 高階関数はコールバック関数を引数にもつ関数
function 高階関数(コールバック関数){
const 引数 = '引数';
コールバック関数(引数); //どのような引数が渡ってくるかは高階関数が決定する。
}
// 高階関数を実行する時
高階関数(console.log);//console.logの引数は高階関数が決定する
高階関数(alert);
高階関数((x)=>{ //無名関数 xは高階関数が決定する
console.log(x);
});
- イベントリスナーや非同期処理にはコールバック関数が利用されている。
// イベントリスナーのコールバック関数はイベントハンドラーと呼ばれている。
// イベント実行したらどのような処理をするか記述する。
const button.onclick = () => { } // これも広義の意味でのコールバック関数になる。
button.addEventLisner('click',()=>{ });
// 非同期処理も 非同期処理完了後の処理をコールバック関数で記述する。
setTimeout(コールバック関数,1000); // 1秒待機するという非同期処理後にコールバック関数が実行される。
- 非同期処理におけるコールバック関数のネストの問題
非同期処理を同期させたい場合に発生する。
引数のcbは非同期処理実行後に実行したい関数
setTimeout(() => {
console.log(`3秒です。`);
}, 3 * 1000);
setTimeout(() => {
console.log(`1秒です。`);
}, 1 * 1000);
非同期で実行される。
1秒です。
3秒です。
同期させたい場合、非同期処理のコールバック関数のなかで実行させるしかない。
非同期処理のコールバック関数のなかで同期させたい処理を実行する。
setTimeout(() => {
console.log(`3秒です。`);
//ネストして実行
setTimeout(() => {
console.log(`1秒です。`);
}, 1 * 1000);
}, 3 * 1000);
同期された。
3秒です。
1秒です。
上のネストを解消する仕組みが Promiseの thenメソッド になる。
Promise
- promise の特徴
インスタン時に同期的にPromiseのコールバック関数が実行される。
thenメソッドやcatchメソッドを発火させる条件は resolve,reject関数を実行させる必要がある。 - thenメソッド
Promiseオブジェクトのメソッド
返り値は新しいpromiseオブジェクト返すが次のthenメソッドを発火させる条件はthenメソッドのcbが実行された後になる。
thenメソッドのコールバック関数は非同期処理で実行される。
thenメソッドのコールバック関数は非同期処理で実行される。
// cbは実行できる値
const cb1 = function(){console.log('成功')};
const cb2 = ()=>{console.log('不成功')};
new Promise((resolve, reject) => {
console.log('(^^)/ 同期 1');
resolve(); //thenメソッドの第1引数を発火させる。
// reject(); //thenメソッドの第2引数を発火させる。
})
// Promiseオブジェクトのメソッド
// cb1は非同期処理で発火 次のthenメソッドの発火条件はcbが完了された後になる。
.then(cb1,cb2)
// thenメソッドの返り値はpromiseオブジェクト返すのでチェーンできる。
.then(cb1,cb2);
console.log('(^^)/ 同期 2');
結果
(^^)/ 同期 1
(^^)/ 同期 2
成功
成功
resolveとrejectでthenメソッドの引数が発火する。
new Promise((resolve, reject) => {
// resolve();
// reject();
}).then(cb1,cb2) // 実行されない。
.then(cb1,cb2); //上のthenメソッドのcbが実行されない限り実行されない。
注意点
- thenメソッドに実行できない値を渡してもエラーにならない。
then メソッドの引数には 必ず コールバック関数(実行可能な値) を渡す。
引数は実行できる値(コールバック関数)を渡す。
// cbは関数を返す。
const cb1 = function(){console.log('成功')};
const cb2 = ()=>console.log('不成功');
new Promise((resolve, reject) => {
console.log('(^^)/ 同期 1');
// resolve と reject を実行しなくても
// resolve();
// reject();
// thenメソッドに 実行でききない引数をわたしてもエラーにならない。
}).then(cb1(),cb2()); // 第1,2 引数どちらとも同期処理で発火する。
console.log('(^^)/ 同期 2');
結果
(^^)/ 同期 1
成功
不成功
(^^)/ 同期 2
thenメソッドは非同期処理なので同期処理完了後、呼び出される。
const returnPromise = (value) => {
return new Promise((resolve) => {
console.log(value); //同期処理
resolve();
});
};
console.log("A"); //コールスタック1
returnPromise("B") //コールスタック2
.then(() => { //非同期処理はバックグラウンドで実行される。
//バックグラウンドで非同期処理が完了するとそのコールバック関数がキューに追加される
console.log("C");
})
.then(() => {
console.log("D");
});
returnPromise("E") //コールスタック3
.then(() => { //非同期処理はバックグラウンドで実行される。
//バックグラウンドで非同期処理が完了するとそのコールバック関数がキューに追加される
console.log("F");
})
.then(() => {
console.log("G");
});
console.log("H");//コールスタック4
結果
A # 同期処理
B # 同期処理
E # 同期処理
H # 同期処理
# 非同期処理は同期処理が完了してから実行される。
C # 非同期処理のコールバック関数
F # 非同期処理のコールバック関数
D # 非同期処理のコールバック関数後の非同期処理のコールバック関数
G # 非同期処理のコールバック関数後の非同期処理のコールバック関数
コールスタック キュー
----------------|----------------
console.log("A")|
console.log("B")|
| バックグラウンドで実行された非同期処理の完了後,そのCBがキューに追加される
| -> console.log("C")
console.log("E")|
| -> console.log("F")
console.log("H")|
-----------------------------------
コールスタック キュー
----------------|----------------
console.log("C")|
| -> console.log("D")
console.log("F")|
| -> console.log("G")
-----------------------------------
コールスタック キュー
----------------|----------------
console.log("D")|
console.log("G")|
非同期処理のチェーン
- Promiseの resolveとreject は 非同期処理の コールバック関数の中で呼びだす。
- 順番
- 非同期処理
- 非同期処理のCBで resolve,reject を実行する。
- それを受けてthenメソッドの引数が発火する。
const asyncFunc = (time, r=null) => {
setTimeout(() => {
console.log(`${time}秒です。`);
// 非同期処理のcbで resolve or reject を呼び出す。
r && r();
}, time * 1000);
};
new Promise((resolve)=>{
asyncFunc(3,resolve);
}).then(()=>{
return new Promise((resolve)=>{
asyncFunc(2,resolve);
});
}).then(()=>{
return new Promise((resolve)=>{
asyncFunc(1,resolve);
});
});
結果同期で実行される。
3秒です。
2秒です。
1秒です。
注意点
thenメソッドで返ってくるPromiseの完了条件がCB実行後になるため、
thenメソッドでは必ず新しいPromiseのインスタンスでthenメソッドの返り値を上書して、完了条件をresolve,reject実行後にする必要がある。
thenメソッドでPromiseをリターンしない場合
new Promise() // --- 1⃣
.then()
.then(); // 上のthenメソッドが返すPromiseでチェーンできる。
new Promise() // --- 1⃣
.then(()=>{ // 1番目のPromise の resolve を 受けて引数が発火する。
// new Promise をreturn しない場合
new Promise(); // // --- 2⃣
})
.then(); // 上のthenメソッドのCBが実行されると発火される。
new Promise() // --- 1⃣
.then(()=>{
return new Promise(); // --- 2⃣
})
.then();//2番目のPromiseのメソッドになる。
// 👆 よって 2番目のPromise の resolve,reject を 受けて引数が発火する。
thenメソッドでPromiseをリターンしない場合 実際の例
// 非同期関数
const asyncFunc = (time,r) =>{
setTimeout(()=>{
console.log(`${time}秒です。`)
// 非同期処理のcbで resolve,reject を呼び出す
r();
},time*1000);
}
new Promise((r)=>{ // -- 1⃣
console.log('(^^)/ 同期 1');
asyncFunc(3,r);
})
.then(()=>{ // -- 1⃣
// Promise を リターンしない。
new Promise((r) => {
console.log('1のthenメソッド');
asyncFunc(2, r);
});
})
.then(()=>{ // 👆のthenメソッドのcb関数実行後、非同期処理の完了をまたずに発火する。
new Promise((r) => {
console.log('2のthenメソッド');
asyncFunc(1, r);
});
});
console.log('(^^)/ 同期 2');
結果
(^^)/ 同期 1
(^^)/ 同期 2
3秒です。
1のthenメソッド
2のthenメソッド
1秒です。
2秒です。
新しいPromiseのインスタンスを返す場合
new Promise((r) => { // -- 1⃣
asyncFunc(3, r);
})
// 1⃣のresolveをうけて 1⃣のthenメソッド は非同期処理で発火
// 今度は 1⃣のthenメソッドの resolve を待って 2⃣のthenメソッドが発火する。
.then(() => { // -- 1⃣
// new Promise((r) => {
return new Promise((r) => { // -- 2⃣
console.log('1のthenメソッド');
asyncFunc(2, r);
});
})
.then(() => { // -- 2⃣ // 2⃣のプロミスのthenメソッドになるため
// 2⃣のresolveをまって発火する。
return new Promise((r) => {
console.log('2のthenメソッド');
asyncFunc(1, r);
});
});
結果
3秒です。
1のthenメソッド
2秒です。
2のthenメソッド
1秒です。
- thenメソッドはPromiseを返すのでthenメソッドでチェーンできる。
- Promiseのresolve,rejectは、非同期処理のコールバック関数で呼び出す。
要するに非同期処理のコールバック関数で記述していた次の非同期処理をresolve,rejectで置き換えただけ。
resolve,reject の引数
- ローカルスコープ2⃣からローカルスコープ1⃣の変数にアクセスしたい場合
resolveとrejectの引数に変数を渡すことでthenメソッドの引数に値を渡せる。 - 引数のバケツリレー
ローカルスコープで囲まれているため、渡したい変数や値は引数でバケツリレーする必要がある。
スコープについて
// グローバルスコープ
new Promise((resolve,reject)=>{
// ローカルスコープ --1⃣
resolve(実引数1);
// アクセスできるスコープは
// グローバルスコープとローカルスコープ1⃣
})
.then((仮引数2)=>{ // この仮引数2 に 実引数1 が渡ってくる。
// ローカルスコープ --2⃣
// アクセスできるスコープは
// グローバルスコープと仮引数2とローカルスコープ2⃣
return 実引数2;
})
.then((仮引数3)=>{ // この仮引数3 に thenメソッドでreturnした値が渡る。
// ローカルスコープ --3⃣
// アクセスできるスコープは
// グローバルスコープと仮引数3とローカルスコープ3⃣
});
reject関数について
1 thenメソッドの第2引数を発火させる。
new Promise(function(resolve,reject){
setTimeout(function(){
reject();
}, 1000);
})
.then(cb1,cb2); //cb2が発火する
2 第2引数がない場合は、catchメソッドを発火させる。
new Promise(function(resolve,reject){
setTimeout(function(){
reject();
}, 1000);
})
.then(cb1)
.then(cb1)
.catch(cb1);//catchメソッドが発火する
3 両方混在している場合
new Promise(function(resolve,reject){
setTimeout(function(){
reject();
}, 1000);
})
.then(cb1,cb2) //cb2が発火
.then(cb1,cb2) //cb1が発火
.catch(cb1); //直のthenメソッドで例外が発生した場合発火
async と await
Promiseの引数のバケツリレーを回避した非同期処理を同期させるための
Promiseにかわる新しい書き方
// 返り値がCBによるブロックスコープを形成しないため
// 他のPromiseからもアクセスできるようになる。
(async () => {
// awaitの戻り委はPromiseを返す必要がある
const 引数1 = await new Promise((r) => {
asyncFunc(3, r.bind(null, '引数1'));
});
const 引数2 = await new Promise((r) => {
asyncFunc(2, r);
console.log(引数1);
});
const 引数3 = await new Promise((r) => {
asyncFunc(1, r);
console.log(引数1);
});
})();
async await の reject 処理
try{}catch(e){}で処理する。
(async ()=>{
try{
const 引数1 = await new Promise((r,j)=>{
j('エラーです。');
});
}catch(e){
console.log(e);
}
})();
Promise でのエラーハンドリング
promiseには暗黙のtry catchがある。
new Promise(function (resolve, reject) {
resolve("ok");
// reject(new Error("Whoops!"));
}).then(function (result) {
// throw new Error("Whoops!");
blabla(); // error
}).catch(alert); // エラーの取得
非同期処理の中でエラーが生じたときのエラーハンドリング
注意事項-これはcatchしない。
new Promise(function(resolve, reject) {
// noSuchVariable //ここで死んだ場合はcatchしてくれる。
setTimeout(() => {
// 非同期処理のCBでエラー
throw new Error("Whoops!");
}, 1000);
}).catch(e=>alert(e)); //非同期処理のエラーは取得できない
これもpromiseのcatchではcatchしない。
new Promise(function(resolve, reject) {
setTimeout(() => {
try{
noSuchVariable; // スクリプトはここで死にます
}catch{ //ここではチャッチしてくる。
throw new Error("Whoops!");
}
}, 1000);
}).catch(e=>alert(e)); //ここではチャッチしてくれない。
こうする必要がある。
new Promise(function (resolve, reject) {
setTimeout(() => {
try {
noSuchVariable; // スクリプトはここで死にます
} catch {
// throw new Error("Whoops!");
reject("Whoops!");
}
}, 1000);
}).catch(e => alert(e)); //ここでチャッチしてくる。
発展 イテレーターと非同期処理
const arr = [700, 600, 500];
(async () => {
// const res = await arr.map( // mapの戻り値は配列
// promise ではないので await は無意味
})();
//cbをasyncにしても、map 関数の各イテレーションでブロックされるわけではない。
const res = arr.map(async (time) => {
const res = await new Promise((r) => {
setTimeout(() => {
// console.log(`exec: ${time}`);
r(`resolve ${time}`);
}, time);
});
console.log(res);
return res;
});
console.log(res);
結果
[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
resolve 500
resolve 600
resolve 700
map 関数の各イテレーションでブロックされるわけではない。
const arr = [700, 600, 500];
// asyncの戻り値もPROMISE
const asyncCb = async (time) => {
const res = await new Promise((r) => {
setTimeout(() => {
r(`resolve ${time}`);
}, time);
});
console.log(res);
return res;
};
const myMap = (arr,cb)=>{
const res = [];
for (let i = 0; i < arr.length; i++) {
const time = arr[i];
// ここでawaitされているわけではない。
res.push(cb(time));
}
return res;
};
const res = myMap(arr,asyncCb);
console.log(res);
結果
[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
resolve 500
resolve 600
resolve 700
イテレーションを実行時に await を使用
const arr = [700, 600, 500];
const asyncCb = async (time) => {
const res = await new Promise((r) => {
setTimeout(() => {
r(`resolve ${time}`);
}, time);
});
console.log(res);
return res;
};
const myMap = async (arr,cb)=>{
const res = [];
for (let i = 0; i < arr.length; i++) {
const time = arr[i];
// ここでawait
res.push(await cb(time));
}
return res;
};
(async ()=>{
const res = await myMap(arr,asyncCb);
console.log(res);
})()
結果
resolve 700
resolve 600
resolve 500
[ 'resolve 700', 'resolve 600', 'resolve 500' ]
各イテレーションごとに待機するため、処理が非常に遅くなる。
Promise の 静的メソッド
promiseオブジェクトの配列を処理する。
各Promiseは並列で処理される。
- allメソッド
全てのPromiseが解決したらthenメソッドの第1引数が発火する。
戻り値は 各Promiseの結果の配列 になる。
どれか一つでもrejectならrejectのタイミングで第2引数が発火する。
並列に処理するため処理が早くなる。
const arr = [700, 600, 500];
(async () => {
const res = await Promise.all(arr.map((time) => {
return new Promise((r) => {
setTimeout(() => {
console.log(`exec: ${time}`);
r(`resolve ${time}`);
}, time);
});
}));
console.log(res);
})();
結果 並列処理されている
exec: 500
exec: 600
exec: 700
[ 'resolve 700', 'resolve 600', 'resolve 500' ]
静的メソッド2 resolve() or reject()
Promise.resolve
const thennable = {
then(r,j) {
setTimeout(() => {
console.log('(^^)/');
}, 3000);
}
};
// thenableのthenメソッドを実行する。
Promise.resolve(thennable);
// thenableのthenメソッドは実行されない。
Promise.reject(thennable)
Promise.resolveのthenメソッド
const thennable = {
then(r,j) {
setTimeout(() => {
console.log('(^^)/');
// r を実行することで 次のthenメソッドの第1引数が発火する。
r();
}, 3000);
}
};
Promise.resolve(thennable)
// thenableの中で rやj が実行されている必要がある。
.then(
() => {
console.log('resolve');
},
() => {
console.log('reject');
}
);
結果
//3秒後
(^^)/
resolve
// thenableを rejectメソッドで利用することはない。
Promise.reject(thennable)
.then(
() => {
console.log('resolve');
},
() => {
console.log('reject');
}
);
結果
reject