1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

マンガでわかる非同期処理 メモ

Last updated at Posted at 2024-05-27

リファレンスサイト

同期処理と非同期処理

  • 同期処理
    実行した順番に処理が実行される。
  • 非同期処理
    実行した順番に処理が実行されない。
同期処理
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 は 非同期処理の コールバック関数の中で呼びだす。
  • 順番
    1. 非同期処理
    2. 非同期処理のCBで resolve,reject を実行する。
    3. それを受けて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秒です。
  1. thenメソッドはPromiseを返すのでthenメソッドでチェーンできる。
  2. 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は並列で処理される。
image.png
image.png
image.png
image.png

  • allメソッド
    全てのPromiseが解決したらthenメソッドの第1引数が発火する。
    戻り値は 各Promiseの結果の配列 になる。
    どれか一つでもrejectならrejectのタイミングで第2引数が発火する。
    image.png
    image.png
    • async の時
      image.png
      image.png
並列に処理するため処理が早くなる。
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' ]
  • allSettledメソッド
    全ての配列の要素の結果が配列として変える。
    image.png
    image.png

  • anyメソッド
    最初に履行されたプロミスの値を返す
    すべてのプロミスが拒否された場合のみエラーでrejectを返す
    image.png
    image.png

  • raceメソッド
    image.png
    image.png
    image.png

静的メソッド2 resolve() or reject()

  1. promise の テストに使用する。
    image.png

  2. thenメソッドをもったオブジェクト

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
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?