0
0

More than 3 years have passed since last update.

JavaScriptの非同期処理とコールバック、Promiseについてまとめてみた

Last updated at Posted at 2020-10-10

はじめに

JavaScriptの非同期処理とコールバック、Promiseについてあまり理解できていなかったので、自分なりに調べてまとめてみました。
間違いがあればコメント頂けると幸いです。

目次

  • そもそも非同期処理とは
  • 非同期処理のサンプル
  • コールバック(Callback)とは
  • コールバック(Callback)の問題点
  • Promiseとは
  • Promiseの書き方
  • 参考

そもそも非同期処理とは

まず、Promiseを知る上で、必要な概念として、非同期処理があります。
非同期処理とは、「ある処理が実行されてから終わるまで待たずに、次に控えている別の処理を行うこと」です。
ちなみに同期処理とは、「ある処理が実行されてから終わるまで、次に控えている処理は待つこと」です。
具体的に非同期処理が必要とされるものとしては、以下の3種類があります。
1. ネットワーク経由のリクエスト(例えばAjax呼び出し)
2. ファイルシステム関連の操作(ファイルの読み書きなの)
3. 意図的に遅延された操作(例えばアラーム)

上記を例えば同期的に処理すると、
1. 読み込みに10分かかるファイルの読み込みを行う。
2. 読み込みを行っている間、JavaScriptアプリケーションは他の処理を行えなくなります。

もし非同期的に処理すると、
1. 読み込みに10分かかるファイルの読み込みを行う。
2. 読み込みを行っている間、JavaScriptアプリケーションは他の処理を行うことができます。

JavaScriptのアプリケーションは「シングルスレッド」で実行されるので、1度にひとつのことを実行していきます。言い換えると、複数の処理を並列で実行することができません。ただ、最近のコンピューターはマルチコアが一般的であり、マルチタスキングが可能であるため、JavaScriptのアプリケーションでも非同期の処理を実現するために、非同期プログラミングの機能が追加されました。

非同期処理のサンプル

単純な例を見ていきます。組み込み関数setTimeoutは指定の時間だけ実行を遅延するものです。実際に実行してみるとコードの記述順とコードの実行順が異なることがわかります。


console.log('setTimoutの前:' + new Date());
function f() {
    console.log('これは関数fの中:' + new Date());
}
setTimeout(f, 10000);/* 10秒後に関数fを実行。コールバック*/
console.log('setTimeoutの後')
console.log('これもsetTimeoutの後')

/* 実行結果
setTimoutの前:Sat Oct 10 2020 18:40:51 GMT+0900 (GMT+09:00)
setTimeoutの後
これもsetTimeoutの後
これは関数fの中:Sat Oct 10 2020 18:41:01 GMT+0900 (GMT+09:00
*/

コールバック(Callback)とは

先ほどのsetTimeout(f,10000);は、10秒後に関数fを呼び出す、というものでしたが、この10秒後に呼び出される関数fをコールバック関数といいます。つまり、「将来のある時点で呼び出される関数」をコールバック関数といいます。

コールバックを利用することで、次のことができるようになります。
1. 非同期処理の中で、決まった順序で処理を実行したい
2. 関数を渡す形にすることで、非同期処理を実行した後の処理の内容を自由に決めたい

コールバック(Callback)の問題点

コールバックを利用することで、非同期の実行の管理ができますが、複数のコールバックを待つ必要があると管理が難しくなる問題点があります。これをコールバック地獄といいます。


/* Nodeで実行 */
const fs = require('fs');

fs.readFile('a.txt', function (err, dataA) {
    if (err) console.error(err);
    fs.readFile('b.txt', function (err, dataB) {
        if (err) console.error(err);
        fs.readFile('c.txt', function (err, dataC) {
            if (err) console.error(err);
            fs.writeFile('d.txt', dataA + dataB + dataC, function (err) {
                if (err) console.error(err);
            });
        });
    });
});

これは3個のテキストファイルを読み込み、それらを1つのファイルに書き出すという処理ですが、深い入れ子のブロックができあがってしまいます。これでは保守性が悪いため、より安全で保守しやすいコードを記述するために、Promiseが考案されました。

Promiseとは

Promiseとは、非同期処理に対するオブジェクトとルールを仕様化することで、複雑な非同期処理をパターンに沿って記述、実行できるようになった仕組みのことをいいます。

先ほどの3個のテキストファイルを読み込み、それらを1つのファイルに書き出すという処理をPromiseに置き換えてみます。


function readFile(file){
    return new Promise((resolve,reject)=>{
        fs.readFile(file,(err,data)=>{
            if(err) {reject(err); return;}
            resolve(data)
        })
    })
}

function writeFile(file, contents){
    return new Promise( (resolve,reject) =>{
        fs.writeFile(file, contents, (err) => {
            if(err) {reject(err); return;}
            resolve("書き込み成功");
        })
    })
}
let totalData;
readFile('a.txt')
    .then((data)=>{
        totalData = data;
        return readFile('b.txt')
    })
    .then((data)=>{
        totalData += data;
        return readFile('c.txt')
    })
    .then((data)=>{
        totalData += data;
        return writeFile('d.txt',totalData)
    })

先ほどよりコードが長くなってしまいましたが、Promiseにした方が見やすくはなったのではないでしょうか。

Promiseの書き方

1. Promiseオブジェクトの作成

Promiseを利用するには、Promiseのオブジェクトを生成していきます。オブジェクトの生成は以下の通りです。
1. new Promise(fn) の返り値がpromiseオブジェクト
2. fn には非同期等の何らかの処理を書く

  • 処理結果が正常なら、resolve(結果の値) を呼ぶ
  • 処理結果がエラーなら、reject(Errorオブジェクト) を呼ぶ

つまり、下記のようなコードになります。


new Promise((resolve, reject) => {
    /* 非同期の処理を記述 */
    setTimeout(() => {
        resolve('Hello World');
    }, 10000);
})

/* 上記をconsole.log()で確認してみた結果は、以下の通りで、promiseオブジェクトが返ってきている。
Promise { <pending> }
*/

2. Promiseオブジェクトへの処理を記述

先ほど作成したPromiseオブジェクトを返す関数を実装してみます。


function asyncFunction() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Hello World');
        }, 10000);
    })
}

asyncFunction()
/* asyncFunction()をconsole.log()で確認してみた結果は、以下の通り。
Promise { <pending> }
*/

ただし、このままですと、戻されるPromiseを何も利用できていないので、Promiseに用意されているthenメソッドを利用していきます。


function asyncFunction() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Hello World');
        }, 10000);
    })
}

asyncFunction().then(
  /* 非同期処理成功時に行う処理を記述 */
  function onFulfilled(value){
    console.log(value)
}).catch(
  /* 非同期処理失敗時に行う処理を記述 */
  function onRejected(err){
    console.log(err)
})

/* 実行結果
10秒後にHello Worldが出力される。
*/

これをアロー関数を使ってみると、以下のかたちで書けます。


function asyncFunction() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Hello World');
        }, 10000);
    })
}

asyncFunction().then((value) => {
    console.log(value)
}).catch((err) => {
    console.log(err)
})

/* 実行結果
10秒後にHello Worldが出力される。
*/

参考

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