30
12

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 5 years have passed since last update.

JavaScriptにおける順次実行をPromiseとasync/awaitでできました!

Last updated at Posted at 2018-12-11

概要

 サーバーからデータを受け取るアプリを作成した際にJavaScriptの順次実行に悪戦苦闘したので、その際に調べたことを本記事にまとめておきたいと思います。


目次

  1. 同期処理と非同期処理
  2. 順次実行
  3. 順次実行とエラー処理
  4. 複雑な順次実行
  5. 最後に

同期処理と非同期処理

 JavaScriptでは、基本的に上から順に1つずつ実行されます。上から順に1つずつ実行することを同期処理と言います。次の sample1.js では、①を実行した後に②を実行しているので、以下のようになります。

sample1.js
console.log("start");   // ①
console.log("end");     // ②
sample1.jsの実行結果
start
end

 次の sample2.js では、APIからデータを取得しています。データをまとめて送るなどといった処理はすべてサーバ側で行われ、データが送られてきます。このように他の場所で実行してくれる処理を非同期処理と言います。(※ sample2.js 以降ではjQueryも使用しています。)

sample2.js
$.ajax({
    type: "GET",
    url: "APIのURL",
    data: "送信するデータ",
    dataType: "送信するデータ型",
    contentType: "受信するデータ型"
// 成功時の処理
}).done(function(response, textStatus, jqXHR){
    var message = "success";
    console.log(message);
});
sample2.jsの実行結果
success

 それでは、同期処理と非同期処理を混ぜてみます。次の sample3.js はAPIからデータを取得し、その成否を表示しています。

sample3.js
main=()=>{
    console.log("START");           // ①
    var message = getData("info");  // ②
    console.log(message);           // ③
    console.log("END");             // ④
}

// APIからデータ取得
getData=(sendData)=>{
    console.log("getData begin");
    $.ajax({
        type: "GET",
        url: "APIのURL",
        data: sendData,
        dataType: "受信するデータ型",
        contentType: "送信するデータ型"
    // 成功時の処理
    }).done(function(response, textStatus, jqXHR){
        var message = "success";
        console.log("getData finish");
        return message;
    });
}
sample3.jsの予想結果
START
getData begin
getData finish
success
END

 上から①、②、③、④の順に実行されると、上記の sample3.jsの予想結果 になります。しかし、非同期処理は実行を他の場所に任せている分、その間に次の処理を同時に行うことができ、並行処理のようになります。そのため、②でリクエストを送り、データを待つ間に、③や④を実行してしまいます。そのため、実際の実行結果は下記の sample3.jsの実行結果 になります。messageはgetDataが終わっていないため、何もないので、undefinedと表示されています。このように、非同期処理が終わるのを待たずに実行されてしまい、実行する順番が入れ替わってしまうことがあります。

sample3.jsの実行結果
START
getData begin
undefined
END
getData finish

 上記の予想結果のようにするにはどうすればよいのでしょうか?

順次実行

 非同期処理を上から順に実行するには、Promiseとasync/awaitを使用するのがおすすめです。 sample3.js をPromiseとasync/awaitを用いて書き直したものが次の sample4.js になります。Promiseとasync/awaitを使用することで、awaitがついている関数の処理が終わるまで待ってくれます。

  Promiseとasync/awaitの使い方:
    1. 非同期処理をgetData(sendData)のように1つの関数にまとめておきます
    2. 順次実行したい非同期処理や同期処理をasyncで囲みます(①)
    3. 順次実行したい非同期処理の関数の前にawaitを書きます(②)
    4. 関数にまとめた非同期処理をPromiseで囲みます(③)
    5. 非同期処理が成功した時に返したい値などをresolveに入れます(④)
     ※入れられるのは1つだけなので、複数ある場合は配列にまとめてください

sample4.js
main=()=>{
    (async()=>{                                 // ①
        console.log("START");
        var message = await getData("info");    // ②
        console.log(message);
        console.log("END");
    })();                                       // ①
}

// APIからデータ取得
getData=(sendData)=>{
    return new Promise((resolve, reject)=>{     // ③
        console.log("getData begin");
        $.ajax({
            type: "GET",
            url: "APIのURL",
            data: sendData,
            dataType: "受信するデータ型",
            contentType: "送信するデータ型"
        // 成功時の処理
        }).done(function(response, textStatus, jqXHR){
            var message = "success";
            console.log("getData finish");
            resolve(message);                   // ④
        });
    });
}
sample4.jsの実行結果
START
getData begin
getData finish
success
END

順次実行とエラー処理

 しかし、このままでは、エラー処理がされていません。 sample4.js にエラー処理を追加してみたものが次の sample5.js です。
 エラー処理の場合、asyncに囲まれている部分でエラーが発生した場合に行われる処理をcatchの中に書きます(①)。また、非同期処理の関数では、APIからエラーが返ってきた際の処理をAjaxの場合はfailに書きます(②)。そして、非同期処理が失敗した時に返したい値をrejectに入れます(③)。このrejectに入れた中身がasyncのcatchのerrの中身になります。

sample5.js
main=()=>{
    (async()=>{
        console.log("START");
        var message = await getData("info");
        console.log(message);
        console.log("END");
    })().catch((err)=>{                             // ①
        console.log(err);
        console.log("ERROR END");
    });
}

// APIからデータ取得
getData=(sendData)=>{
    return new Promise((resolve, reject)=>{
        console.log("getData begin");
        $.ajax({
            type: "GET",
            url: "APIのURL",
            data: sendData,
            dataType: "受信するデータ型",
            contentType: "送信するデータ型"
        // 成功時の処理
        }).done((response,textStatus,jqXHR)=>{
            var message = "success";
            console.log("getData finish");
            resolve(message);
        // 失敗時の処理
        }).fail((jqXHR, textStatus, errorThrown)=>{ // ②
            var message = "failed";
            console.log("getData finish");
            reject(message);                        // ③
        });
    });
}
sample5.jsの実行結果(成功時)
START
getData begin
getData finish
success
END
sample5.jsの実行結果(失敗時)
START
getData begin
getData finish
failed
ERROR END

複雑な順次実行

 実はPromiseの中にasyncを書き、入れ子構造にすることができます。そのため、非同期処理の繰り返しも順次実行することができます。その一例を sample6.js に示します。

sample6.js
main=()=>{
    (async()=>{
        console.log("START");
        var message = await repeat(["info", "name"]);
        console.log(message);
        console.log("END");
    })().catch((err)=>{
        console.log(err);
        console.log("ERROR END");
    });
}

// 繰り返し処理
repeat=(words)=>{
    return new Promise((resolve,reject)=>{
        (async()=>{
            console.log("repeat begin");
            var multiMessage = [];
            for (var i=0; i<words.length; i++) {
                var message = await getData(words[i]);
                multiMessage.push(message);
            }
            console.log("repeat finish");
            resolve(multiMessage);
        })().catch((err)=>{
            console.log("repeat error");
            reject(err);
        });
    })
}

// APIからデータ取得
getData=(sendData)=>{
    return new Promise((resolve, reject)=>{
        console.log("getData begin");
        $.ajax({
            type: "GET",
            url: "APIのURL",
            data: sendData,
            dataType: "受信するデータ型",
            contentType: "送信するデータ型"
        // 成功時の処理
        }).done((response,textStatus,jqXHR)=>{
            var message = "success";
            console.log("getData finish");
            resolve("success");
        // 失敗時の処理
        }).fail((jqXHR, textStatus, errorThrown)=>{
            var message = "failed";
            console.log("getData finish");
            reject(message);
        });
    });
}
sample6.jsの実行結果(成功時)
START
repeat begin
getData begin           // 1回目のgetData
getData finish
getData begin           // 2回目のgetData
getData finish
repeat finish
["success", "success"]  // 1回目のgetDataの結果と2回目のgetDataの結果
END

最後に

 今回、社内イベントで初めて記事を書かせていただきました。拙いところが多々あるかと思いますが、改善点等ございましたら、教えていただけると幸いです。
 最後まで読んでくださり、ありがとうございました。ヾ(。>﹏<。)ノ゙


参考文献

本記事を書くにあたり、参考にさせていただきました
- Qiita マークダウン記法 一覧表・チートシート
- Markdown記法 チートシート
- JavaScriptの同期、非同期、コールバック、プロミス辺りを整理してみる

30
12
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
30
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?