Javascript > 非同期処理

下位ページ

Content

Promiseで、終了後に発火させる


同期は追い越さない、非同期は追い越す

同期処理は、「追い越しが掛からない」
console.log("ステップ1");
alert("ステップ2");
console.log("ステップ3");
ステップ1 → ステップ2(OKがクリックされるまで待って)→ ステップ3

非同期処理は、「追い越しが掛かる」
console.log("ステップ1");
setTimeout( function() { alert("ステップ2");}, 3000); // 3秒後にダイアログ
console.log("ステップ3");
ステップ1 → ステップ3 → ステップ2

JavaScriptの通信は非同期処理

var xhr = new XMLHttpRequest();
xhr.open('GET','https://somewhere');
xhr.send();
xhr.addEventListener('load', function(result){console.log("ステップ1");});
console.log("ステップ2");
ステップ2 → ステップ1 に多分なる。通信のセットや通信時間で追い越しが掛かる。そのため、ステップ1の通信結果を使って、ステップ2の処理をしようとすると、まだ値がセットされておらず、エラーになる。

1行メッセージを出すくらいなら、function(result){} の中に記述してしまえばよいが(ネスト、入子)、
  • ステップ1の結果を使って、ステップ2の通信を制御
  • ステップ2の結果を使って、ステップ3の通信を制御
  • ステップ3の結果を使って、ステップ4の通信を制御
  • (以下繰り返し)
となると、どんどんネストが深くなる。

Promise処理


  1. Promiseオブジェクト返す関数をつくる。
  2. この関数の中で、処理が終わったらresolve(value)を実行。(valueは戻り値、としておく)
  3. Promise.then(...)を実行すると、thenの中は、resolveが実行されるまで止まっている。

こんなプログラムを作ってみる。

function testAjax1() {
 
    console.log("Start1");
 
    var func1 = function() {
        console.log("step1");
        return new Promise(function(resolve, reject){
            setTimeout(function() {
                console.log("step1 timeout");
                resolve("step1 value");
            }, 3000);
        });
    }
 
    alert("alert stop");
 
    var func2 = function(arg) {
        console.log("step2");
        console.log(arg);
    }
 
    func1().then(func2);
    console.log("End1");
}
 

実行すると
  1. Start1
  2. (func1関数(変数)を宣言しただけでは実行はされない)
  3. (alertで一旦止まる → OK で再開)
  4. step1(func1()で実行される)
  5. End1(func1のsetTimeoutが効いているので、先にこっちが表示される)
  6. step1 timeout(setTimeoutが切れて、表示
  7. step2(func1のresolveが返って、func2が実行される)
  8. step1 value(func2が、func1のresolveの戻り値を受け取っている)

func1()のあとに出てくる End1 は、setTimeout を追い越して処理されていることに注意。

Promiseの数珠つなぎ


Promiseの処理を、数珠つないでみる。
変更点は
  • func2内にsetTimeoutの処理を追加
  • func3を追加
  • func1().then(func2).then(func3)に変更

function testAjax1() {
 
    console.log("Start1");
 
    var func1 = function() {
        console.log("step1");
        return new Promise(function(resolve, reject){
            setTimeout(function() {
                console.log("step1 timeout");
                resolve("step1 value");
            }, 3000);
        });
    }
 
    alert("alert stop");
 
    var func2 = function(arg) {
        console.log("step2");
        console.log(arg);
    }
 
    var func2 = function(arg) {
        console.log("step2");
        console.log(arg);
 
        setTimeout(function() {
            console.log("step2 timeout");
        }, 3000);
       };
 
    var func3 = function(arg) {
        console.log("step3")
        console.log(arg);
    }
 
    func1().then(func2).then(func3);
    console.log("End1");
}
 

実行すると
  1. (途中まで略)
  2. step2
  3. step1 value
  4. step3
  5. undefined
  6. step2 timeout

つまり、func2のsetTimeout処理(の終了)を待たずに、func3を実行している。そこで、さらに変更を加える。
変更点は
  • func2もPromise処理を加える

var func2 = function(arg) {
        return new Promise(function(resolve, reject){
            console.log("step2");
            console.log(arg);
 
            setTimeout(function(){
                console.log("step2 timeout");
                resolve("step2 value");
            },3000);
        });
    }
 

実行すると
  1. (途中まで略)
  2. step2
  3. step1 value
  4. step2 timeout
  5. step3
  6. step1 value

もともとfunc1のPromiseから始まった処理だったが、func2のPromise処理も数珠つなぎにできた。

for 文の中でPromiseを回す(直列実行)

同じ "funcPromise" を対象とする集合の各要素に対して、直列に実行。
配列の複数の要素など、for ループを使って実行したいときに、どうするのか。あーなるほど、というやり方。
PromiseによるJavaScript非同期処理レシピ集 - Subterranean Flower Blog
// 空の解決済みPromiseを生成 <- 最初のプロミスはresolve
let promise = Promise.resolve();
 
  for(const id of dataIds) {
    // promiseにthenで繋ぎ再代入
    // これでどんどんthenをチェーンしていける
    // ちなみに funcPromise は Promise を返す関数 
    promise = promise.then(() => funcPromise(id));
  }
 

1回目は resolve で入って、 funcPromise を実行する。
Promise オブジェクトが帰るので、2回目の実行は待ち状態になっている

for 文の中でPromiseを回す(並列実行)

配列の複数の要素に実行したときに、全部の処理が終わったことを確認して、「まとめの処理」を行いたいときがある。
そのような場合は、Promise.all(...) を使う。やっていることは簡単だが、関数としてして実装するときに、.then についてちゃんと理解していなかったので、小一時間悩んでしまった。

// 空の解決済みPromiseを生成 <- 最初のプロミスはresolveconst 
 
  // 大元の関数に対する return
  return firstFunc().then(()=>{
    promises = [];
 
    for(const id of dataIds) {
      // primise をどんどんためていく 
      promises.push(funcPromise(id));
    }
 
    return Promise.all(promises)  // 全部揃ったらresolve() を返すPromiseを戻す
  }
 
 

自分のハマリポイントは .then(()=>{... return Promise.all(promises)}) しか、書かなかったところ。一番大元の関数の戻り地として return すべきところを、then で実行する関数の return としてしか書かなかったところで、うまくうごかない。。。となってしまった。

Promise.all() - MDN
返り値はすべてのPromise(成功すれば、resolveで戻ってくる値を配列にしたもの)

async/awaitを使って書く

Promise を完結に書く方法。自分のpleidesで、リントがエラー扱いするので使いたくないが、forループで実現するときに、これ以外の方法が思い浮かんでいない。
使う関数を
async func(){}
で宣言する。
呼び出す際に
await func()
にすると、resolveされるまで、次の処理を待つ
https://qiita.com/soarflat/items/1a9613e023200bbebcb3
最終更新:2020年09月13日 18:29