JavaScriptプログラム

Promiseを用いて、非同期関数を同期的に動かす

Promiseを用いて、JavaScriptの非同期関数を同期的に動かします。
本記事は、記事「setTimeoutを用いて、非同期関数を同期的に動かす」の続きになります。

基本はsetTimeoutですが、setTimeoutを利用すると「見た目」が複雑になります。
setTimeoutにPromiseを追加することで、「見た目」がシンプルになります。

(注)「見た目」が複雑
プログラムのネスト(nest、入れ子)が多段階になり、ネストが深くなった状態。
プログラムの可読性が低い状態。

(注)「見た目」がシンプル
プログラムのネストが浅くフラットな状態。プログラムの可読性が高い。

 

 

同期的と非同期的

同期的に動く

プログラムが「同期的に動く」とは、プログラムの上から下に向かって、順番に処理が進むことです。

 

プログラム1

ボタンをクリックすると、関数A()、B()、C() を実行するプログラム。

$('#btn').on('click', function() {
    A();
    B();
    C();
});

 

プログラム1で、上から順番に処理が進んだ場合、同期的に動いたことになります。

  1.  A()が動く
  2.  B()が動く
  3.  C()が動く

 

 

非同期的に動く

プログラムが「非同期的に動く」とは、「同期的には動かない」ことです。

プログラム1で、同期的に動かなかった場合、非同期的に動いたことになります。

例えば、

  1.  C()が動く
  2.  A()が動く
  3.  B()が動く

 

 

同期関数と非同期関数

同期関数

同期関数は同期的に動きます(同期処理)。

 

プログラム2
ボタンをクリックすると、テキストエリアに数字を表示するプログラム。

$('#btn').on('click', function() {
    $("#textarea").html('10');		// 同期関数
    $("#textarea").html('20');		// 同期関数
    $("#textarea").html('30');		// 同期関数
    $("#textarea").html('40');		// 同期関数
});

 

プログラム2は、上から下に向かって順番に1行ずつ処理が進みます。

  1.  テキストエリアに「10」と表示
  2.  テキストエリアに「20」と表示
  3.  テキストエリアに「30」と表示
  4.  テキストエリアに「40」と表示

 

非同期関数

非同期関数は非同期的に動きます(非同期処理)。

 

例えば、ボタンをクリックすると、次の動きをするプログラムを作りたいとします。

仕様A

  1.  テキストエリアを、1秒かけて非表示にする(スライドアップ)
  2.  テキストエリアに、「10」と表示
  3.  テキストエリアを、1秒かけて表示する(スライドダウン)
  4.  テキストエリアに、「20」と表示

 

仕様Aを、単純にプログラムにすると、以下となります。

プログラム3

$('#btn').on('click', function() {
    $("#textarea").slideUp(1000);        // 非同期関数
    $("#textarea").html('10');           //  同期関数
    $("#textarea").slideDown(1000);      // 非同期関数
    $("#textarea").html('20');           //  同期関数
});

 

(注)slideUp(1000)
スライドアップ(slideUP)は、要素を上方向にスライドして隠します。
数字の1000は、デュレーション(duration)で、スライド開始から完了までの時間です。
単位はm秒で、1000と書けば、1000m秒すなわち1秒となります。

(注)slideDown(1000)
スライドダウン(slideDown)は、隠れている要素を下方向にスライドして表示します。
数字の1000は、デュレーション(duration)です。

 

プログラム3の、slideUp()、slideDown()は非同期関数です。
そのため、プログラム3は非同期的に動きます。

実際に動作させると、以下の順に動きます。

  1.  テキストエリアに、「10」と表示
  2.  テキストエリアに、「20」と表示
  3.  テキストエリアを、1秒かけて非表示にする(スライドアップ)
  4.  テキストエリアを、1秒かけて表示する(スライドダウン)

 

この動きは、意図した動き(仕様A)になっていません。

仕様A通りに動くプログラムを作るには、非同期関数を同期的に動かす必要があります。

 

 

非同期関数を同期的に動かす

 

setTimeoutを用いた同期処理

 

setTimeoutを用いることで、非同期関数を同期的に動かすことができます。

 

プログラム4

A();                            // 非同期関数
setTimeout( function() {
    B();                        //  同期関数
}, duration_A);

 

プログラム4の説明

非同期関数A()の処理時間がduration_Aの場合、setTimeoutを用いて、同期関数B()をduration_A後に動かしています。

1. 非同期関数A()が動き始め、duration_A後に動き終わる
2. 同期関数B()が動く

setTimeoutを用いることで、A()が動き、その後にB()が動いています。

(注)詳細は、記事「setTimeoutを用いて、非同期関数を同期的に動かす」をご参照ください。

 

仕様Aを、setTimeoutを用いたプログラムにします(プログラム5)。

プログラム5

<!DOCTYPE html>
<html lang="jp">
    <head>
        <meta charset="utf-8">
        <title>Control Asynchronous Function Synchronously</title>
        <script src="../lib/jquery.min.js"></script>
        <script>
            var duration_A = 2000;
            var duration_C = 2000;
		
            $(function(){
                $('#btn').on('click', function() {
                    A();                                // Asynchro. Func.
                    setTimeout(function() {
                        B();                            //  Synchro. Func.
                        C();                            // Asynchro. Func.
                        setTimeout(function() {
                            D();                        //  Synchro. Func.
                        }, duration_C);
                    }, duration_A);
                });
            });
 
            function A() {
                $("#textarea").slideUp(duration_A);
            }
 
            function B() {
                $("#textarea").html('10');
            }
 
            function C() {
                $("#textarea").slideDown(duration_C);
            }
		
            function D() {
                $("#textarea").html('20');
            }
        </script>
    </head>
    <body>
        <input type="button" id="btn" value="Click"><br>
        <textarea id="textarea" cols="50" rows="10" readonly></textarea>
    </body>
</html>

 

 

プログラムの説明

グローバル変数
・duration_A: スライドアップのデュレーションです。2秒に設定。
・duration_C: スライドダウンのデュレーションです。2秒に設定。

 

ボタンをクリックすると、以下の順で、同期的にプログラムが動きます。

  1.  13行目: A()が動き始め、2秒後に動き終わる
  2.  15行目: B()が動く
  3.  16行目: C()が動き始め、2秒後に動き終わる
  4.  18行目: D()が動く

 

 

Promiseを用いた同期処理

基本的には、上述した「setTimeoutを用いた同期処理」です。
それに、DeferredやPromiseを追加することで、「ネストが深くなる」難点を回避します。

 

プログラムの変形

非同期関数A()の次の処理(ここでは、B())を、以下の手順で変形します。

function B() {                   // 元のプログラム
    $("#textarea").html('10');
}

機能: テキストエリアに、「10」と表示する

 

まず、setTimeoutを用いて、同期処理に対応するよう変形します。

function B() {
    setTimeout(function(){
        $("#textarea").html('10');
    }, duration_A);
}

機能: duration_A後に、テキストエリアに「10」と表示する

 

次に、DeferredやPromiseを追加します(2、5、7行目)。

function B() {
    var defer = new $.Deferred();
    setTimeout(function(){
        $("#textarea").html('10');
        defer.resolve();
    }, duration_A);
    return defer.promise();
}

機能:

  • 2行目: deferを生成
  • 7行目: promiseを返す(promiseの状態は「pending」)
  • 4行目: duration_A後、テキストエリアに「10」と表示
  • 5行目: duration_A後、promiseの状態を「resolve」に変更

 

 

プログラム6

A();
var promise = B();
promise.then( C );

function B() {
    var defer = new $.Deferred();
    setTimeout(function(){
        $("#textarea").html('10');
        defer.resolve();
    }, duration_A);
    return defer.promise();
}

プログラム6は、以下の流れで動作します。

  1.  1行目: A() が動作
  2.  2行目: B() を呼出し、B() の返り値であるpromise(状態: pending)を保持
  3.  3行目: .then(promiseの状態が「resolve」に変化したら)、C()を実行

 

 

duration_A後に実行されるのは、以下となります。

  •  テキストエリアに「10」と表示
  •  C()

 

仕様Aを、Promiseを用いたプログラムにします(プログラム7)。

プログラム7

<!DOCTYPE html>
<html lang="jp">
    <head>
        <meta charset="utf-8">
        <title>Control Asynchronous Function Synchronously</title>
        <script src="../lib/jquery.min.js"></script>
        <script>
            var duration_A = 2000;
            var duration_C = 2000;

            $(function(){
                $('#btn').on('click', function() {
                    A();                        // Asynch. Func.
                    B()                         //  Synch. Func.
                        .then( C )              // Asynch. Func.
                        .then( D );             //  Synch. Func.

                    function A() {
                        $("#textarea").slideUp(duration_A);
                    }

                    function B() {
                        var defer = new $.Deferred();
                        setTimeout(function(){
                            $("#textarea").html('10');
                            defer.resolve();
                        }, duration_A);
                        return defer.promise();
                    }

                    function C() {
                        $("#textarea").slideDown(duration_C);
                    }

                    function D() {
                        var defer = new $.Deferred();
                        setTimeout(function(){
                            $("#textarea").html('20');
                            defer.resolve();
                        }, duration_C);
                        return defer.promise();
                    }
                });
            });
        </script>
    </head>
    <body>
        <input type="button" id="btn" value="Click"><br>
        <textarea id="textarea" cols="50" rows="10" readonly></textarea>
    </body>
</html>

プログラムの説明

  • 14-16行目: Promiseによる連結

 

.then() は必ずPromiseを返します。

そのため、.then()で返したPromiseは以下のように連結できます。

var promise1 = B();
var promise2 = promise1.then( C );
var promise3 = promise2.then( D );

 

また、以下のように表記を簡略化できます。

B()
    .then( C )
    .then( D );

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です