2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Javascript callback、promise、 await-async って何? どんな時に使うの?【備忘録】

Posted at

はじめに

意識せずにcallback関数、promise、await-async を使ってたので改めて勉強してまとめてみた.
( Javascriptの生い立ちからスーパーザックリですが )
他にも面白いJavascript 豆知識があればコメントで教えてください.

本記事のゴール

Javascriptで書く非同期処理の世界観を感じとる.

Javascript とは

シェルスクリプトでいう sleep に当たる関数がJavascriptにはありません.
何故ならJavascriptは元々非同期を想定して設計された言語であるからです.
無理やり sleep を再現することは可能ですが、下手すりゃブラウザがフリーズして操作ができなくなってしまいますね :(

※ [Generator] (https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Generator)を使うやり方もあることはある.

それらの理由があり setTimeout のような関数を使って、
非同期に処理を行うことが推奨されています.

しかしここで一つ問題があります.
以下のような処理を実装しようと考えてみてください.

  1. 0 を出力する
  2. 1秒待つ
  3. 1 を出力する
  4. 1秒待つ

素直に Javascript で記述するとこんな感じ.
※ [Generator] (https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Generator)を使うやり方で入れ子状態になった関数を平準化する方法もあるとか.

index.js

console.log(0);
setTimeout(function(){
  console.log(1);
  setTimeout(function(){
    console.log(2);
  }, 1000);
}, 1000);

上記のようなコールバック地獄になってしまいます.
可読性も低いし、気持ち悪いです🤮
またはこんな感じで書くこともできます.

index.js
function asynchronous (callback, num){
    setTimeout(() => {
        console.log(num)
        callback(num);
    }, 1000);    
}

asynchronous(num => {
    num++;
        asynchronous(num => {
            num++;
                asynchronous(num => {
                num++;
        }, num);
    }, num);
}, 0);

そこで活躍するのが Promise という関数.

Promiseとは

Promise関数を使うことで上記のようなコールバック地獄を
まとめて綺麗に記述することが可能になります.

index.js

function asynchronous (num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(num)
            resolve(num);
        }, 1000);    
    });
}

asynchronous(0).then(num => {
    num++;
    return asynchronous(num);
}).then(num => {
    num++;
    return asynchronous(num);
}).then(num => {
    num++;
    return asynchronous(num);
}).then(num => {
    num++;
    return asynchronous(num);
});

Promise関数の引数となっている resolve は呼ばれたタイミングで次の処理に移ります.
then メソッドは resole が正常に完了後に発火されるメソッドになっています.
reject メソッドはエラーが発生した時のエラーハンドリングとして使用するメソッドです.
then メソッドとは逆に reject が発火したハンドリングメソッドとして catch メソッドを定義します.

index.js

function asynchronous (num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(num);
         if ("何かしらのエラーが発生場合") { 
             reject(num);
       } else {
             resolve(num);
          };
        }, 1000);    
    });
}

asynchronous(0).then(num => {
    num++;
    return asynchronous(num);
}).then(num => {
    num++;
    return asynchronous(num);
}).then(num => {
    num++;
    return asynchronous(num);
}).then(num => {
    num++;
    return asynchronous(num);
}).catch(num => {
    num--;
    console.error('errorだよ.');
    return asynchronous(num);
});

プログラミングをしていると以下のように非同期処理を並列で走らせて、
処理が終了した時に特定の処理を行いたい時があります.
しかし console.log('fire') を1000回繰り返す前に alert が発火してしまいます.

index.js

function fire() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("fire")
            resolve();
        }, 10000);    
    });
}

fire();

alert("終了")

そんな時に使えるのが Promise.all() .

index.js
function fire() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("fire")
            resolve();
        }, 10000);    
    });
};

Promise.all([fire()]).then(() => {
    alert("終了")
})

上記のような fire 関数が複数定義されていて、
一つでも処理が終了したら特定の処理を発火させたい時に使用するのが Promise.race() .

index.js

function fire() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("fire")
            resolve();
        }, 10000);    
    });
}

function fireSecond() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("fireSecond")
            resolve();
        }, 3000);    
    });
}


Promise.race([fire(), fireSecond()]).then(() => {
    alert("終了")
})

十分便利な Promise を凌駕してくるのが async/await !!

async/awaitとは

現時点では対応ブラウザは少ないが、
Promise よりも簡潔に非同期処理が記述できる構文.
冒頭にお話しした以下内容を async/await で記述すると,

  1. 0 を出力する
  2. 1秒待つ
  3. 1 を出力する
  4. 1秒待つ

こうなります.

index.js
function asynchronous (num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(num)
            resolve(num);
        }, 1000);    
    });
}


async function trigger() {
    let num = await asynchronous(0);
    num++;

    num = await asynchronous(num);
    num++;

    num = await asynchronous(num);
    num++;
};

trigger();

とりあえず以下の情報を頭に入れとくと良いと思います.

  • await 関数を内部で使っている場合には、funcitonの先頭に async を付与する.
  • async/awaitのエラーハンドリングは基本的に try/catch.
  • async を使用した場合、返り値がPromiseでラップされた値が返されます.

エラーハンドリング追加コード

index.js
function asynchronous (num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(num)
            resolve(num);
        }, 1000);    
    });
}


async function trigger() {
    let num = 0;
    try {
        num = await asynchronous(0);
        num++;

        num = await asynchronous(num);
        num++;

        num = await asynchronous(num);
        num++;

    } catch(e) {
        throw new Error('Whoops!');
    };
    return num;
};

trigger().then(num => {
    console.log(num, 'Done.')
}).catch(num => {
    console.error('something happend!');
});

async/await のより詳細を知りたい方は以下のリンクがおすすめです.
async/await 入門(JavaScript)

連続した非同期処理を利用する上での注意点は以下を参照.
async/await地獄
async/awaitはコールバック地獄を解消することには成功したが、だからといって不用意に使うと不要な待ち時間が増えてしまいますよ、という内容になっております.

以上.

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?