JavaScript
Node.js
promise
AsyncAwait

とにかくJavascriptで同期処理

想定読者

  • Javascriptでコードを書いてみたのに、上から順に実行されなくて困ってる人
  • "Javascript 同期処理" でググって見たら、Promise, async/await, コールバック地獄, シングルスレッドなどなどよくわからない用語が多発してパンクしそうな人
  • 細かい話はあとで理解すればいいからとにかく同期処理のテンプレが欲しい人

実行環境

  • macOS High Sierra
  • Node.js v9.3.0

コード

* エラー処理は実装していません
async/awaitはNodejs v7.6.0以降しか使えないので注意。

パターン1 : 順次処理

  • f1 -> f2 -> f3 -> f4の順に順次実行
  • 関数から関数への値(正確にはPromise)の受け渡し
  • setTimeoutでプログラムが動いてるっぽく演出

コードサンプル1: Promise 使用

example1.js
function f1(){
    return new Promise((resolve,reject) => {
        console.log("[START]");
        console.log("#1: f1");
        resolve("f1 ==> f2");
    })
}
function f2(passVal){
    return new Promise((resolve,reject) => {
    //setTimeoutは動きをそれっぽくするために入れているだけなので削除可
    setTimeout(
        () => {
            //f1のresolve内の "f1 ==> f2" がpassValに代入される
            console.log(passVal);
            console.log("#2: f2");
            resolve("f2 ==> f3");
         }
         ,Math.random()*2000)
})
}

function f3(passVal){
    return new Promise((resolve,reject) => {
    setTimeout(
        () => {
            console.log(passVal);            
            console.log("#3: f3");
            resolve("f3 ==> f4");
        }
         ,Math.random()*3000)
})
}

function f4(passVal){
    return new Promise((resolve,reject) => {
    setTimeout(
        () => {
            console.log(passVal);
            console.log("#4: f4");
            resolve("f4");
        }
          ,Math.random()*4000)
})
}



//f1->f2->f3->f4の順に実行
f1()
.then(f2)
.then(f3)
.then(f4)
.then((response)=> {
    console.log("Final function: "+response);
    console.log("[END]");
}
)

コードサンプル2: async/await 使用

example2.js
//setTimeoutをいちいち書くのが面倒なので関数化
function Timeout(passVal, ms) {
  return new Promise(resolve =>
      setTimeout(() => {
            resolve(passVal);
    }, ms)
      )
}

async function f1(){
        console.log("#1: f1");
        return "f1 ==> f2";
}
async function f2(passVal){
    console.log("#2: f2");
    return Timeout("f2 ==> f3", Math.random()*2000);
}

async function f3(passVal){
    console.log("#3: f3");
    return Timeout("f3 ==> f4", Math.random()*3000);
}

async function f4(passVal){
        console.log("#4: f4");
        return Timeout("f4", Math.random()*3000);

}


async function runAll(){
    try{
        console.log("[START]");
        const res1 = await f1();
        console.log(res1);

        const res2 = await f2(res1);
        console.log(res2);

        const res3 = await f3(res2);
        console.log(res3);

        const res4 = await f4(res3);
        console.log("Final function: " + res4);

        console.log("[END]");

    }catch(err){
        //エラー処理
        //とりあえず何もしない
    }
}

runAll();

出力結果

[START]
#1: f1
f1 ==> f2
#2: f2
f2 ==> f3
#3: f3
f3 ==> f4
#4: f4
Final function: f4
[END]


パターン2 : 並行処理 & 順次処理

  • f1, f2, f3が全て終わったらf4を実行
  • f1, f2, f3からf4への値(正確にはPromise)の受け渡し
  • setTimeoutの時間をバラつかせてf1~f3の終わるタイミングを毎回変えている

コードサンプル1: Promise 使用

example3.js
function f1(){
    return new Promise((resolve,reject) => {
    console.log("[START]");
    setTimeout(
        () => {
            console.log("#1: f1");
            resolve("f1 ==> f4");
         }
         ,Math.random()*5000)
})
}
function f2(){
    return new Promise((resolve,reject) => {
    setTimeout(
        () => {
            console.log("#2: f2");
            resolve("f2 ==> f4");
         }
         ,Math.random()*6000)
})
}

function f3(){
    return new Promise((resolve,reject) => {
    setTimeout(
        () => {
            console.log("#3: f3");
            resolve("f3 ==> f4");
        }
         ,Math.random()*4000)
})
}

function f4(passVal){
    return new Promise((resolve,reject) => {
    setTimeout(
        () => {
            console.log(passVal);
            console.log("#4: f4");
            resolve("f4");
        }
          ,Math.random()*2000)
})
}



//(f1,f2,f3) => f4の順に実行
Promise.all([f1(),f2(),f3()])
.then(f4)
.then((response)=> {
    console.log("Final function: "+response);
    console.log("[END]");
}
)

コードサンプル2: async/await 使用

function Timeout(passVal, ms) {
  return new Promise(resolve =>
      setTimeout(() => {
            resolve(passVal);
    }, ms)
      )
}

async function f1(){
    console.log("#1: f1");
    return Timeout("f1 ==> f4", Math.random()*5000);
}
async function f2(){
    console.log("#2: f2");
    return Timeout("f2 ==> f4", Math.random()*6000);
}

async function f3(){
    console.log("#3: f3");
    return Timeout("f3 ==> f4", Math.random()*4000);
}

async function f4(){
    console.log("#4: f4");
    return Timeout("f4", Math.random()*2000);

}


async function runAll(){
    try{
        console.log("[START]");

        const res123 = await Promise.all([f1(),f2(),f3()]);
        console.log(res123);

        const res4 = await f4();
        console.log("Final function: " + res4);

        console.log("[END]");

    }catch(err){
        //エラー処理
        //とりあえず何もしない
    }
}

runAll();

出力結果

[START]
#1: f1
#2: f2
#3: f3
[ 'f1 ==> f4', 'f2 ==> f4', 'f3 ==> f4' ]
#4: f4
Final function: f4
[END]

おわりに

 nodejsの非同期処理は最初は私も本当に悩まされました...。ですので、このコードを色々いじって実行順だったり書き方だったりを少しずつ理解していっていただけたらと思います。気が向いたらパターンを追加しようかなと思います。