2018年9月3日 星期一

簡單的 Javascript ( async 與 await)

寫一個回傳 Promise  物件的 Javascript function
// a.js  a Promise function example 
const childprocess = require("child_process");     //  i.e.   /usr/bin/bash
function nextTask(msg) {   console.log(msg+"--- Ok ---")            }
function errTask(msg)    {   console.log(msg+"--- Warring! ---")  }
function bash(cmd) {
    console.log("bash "+cmd)
    return new  Promise(  (ok,ng) => {
          childprocess.exec( cmd, (err, stdout, stderr) =>  err ?  ng(stderr) : ok(stdout)    );
    })
}
async function cmd(opt ) {
    console.log("副程式開始")
    var shell= bash(opt)   // return a Promise
    await shell.then(nextTask).catch(errTask);
    console.log("副程式結束")
}
console.log("主程式開始")
cmd("ls -al").then(  console.log("主程式結束") )
// end of file

執行 js  a.js 看輸出結果:
主程式開始
副程式開始
bash ls  -al
主程式結束
total 12
drwxrwxr-x 2 mint mint 4096  9月  4 14:04 .
drwxrwxr-x 4 mint mint 4096  9月  4 13:05 ..
-rw-rw-r-- 1 mint mint  686  9月  4 14:04 a.js
--- Ok ---
副程式結束

如果將上述 Javascript 內容(a.js)做一些修改, 移除  await 之後,再執行一遍,整個過程變成:
主程式開始
副程式開始
bash ls  -al
副程式結束
主程式結束
total 12
drwxrwxr-x 2 mint mint 4096  9月  4 14:05 .
drwxrwxr-x 4 mint mint 4096  9月  4 13:05 ..
-rw-rw-r-- 1 mint mint  680  9月  4 14:05 a.js
--- Ok ---

可以看到整個非同步(不定時)運作的過程,由此可以了解到 async/await 運作方式, async function 一定回傳一個稱為 Promise 的物件, 而 then( ) 是它的一個從屬函式(member function), await 就只能放在 async function 裏面, 用來等待,作為同步之用, 而 node js 的 child_process.exec 是非同步在運作(也就是說其 callback function 是隨後才會執行)的.要運用 async/await 就不得不瞭解 Promise, 因為 await 就是為了等待特定 promise 完成,才會繼續下一個動作, 但在主程序中的 async function 僅傳回 Promise 就會繼續下一步動作,並不會因 async function 內的 await 就被暫停而隔離(block),簡單就字面意義來說 async function 就是非同步的功能函式,如果要一起執行多個非同步函式又要等待某個非同步函式的完成,應該使用 Promise.all( [  ,   ] ) 平行處理非同步函式,避免在 await 的過程執中阻擋(block)了其它非同步函式的運作,將上述 async function 稍作一點修改:
      async function multicmd(  ) {
           console.log("副程式開始")
           var  shell1= bash("ls -al")  // return a Promise
           var  shell2= bash("pwd")   // return another Promise
           await shell1.then(nextTask).catch(errTask);
           await shell2.then(nextTask).catch(errTask);           
           console.log("副程式結束")
       }
       console.log("主程式開始")
       multicmd( ).then(  console.log("主程式結束")  )

這是輸出結果:
主程式開始
副程式開始
bash ls -al
bash pwd
主程式結束
total 12
drwxrwxr-x 2 mint mint 4096  9月  5 09:19 .
drwxrwxr-x 4 mint mint 4096  9月  4 13:05 ..
-rw-rw-r-- 1 mint mint  782  9月  5 09:19 a.js
--- Ok ---
/home/mint/work/kotlin
--- Ok ---
副程式結束

最後使用 async/await Promise.all 的完整範例:
const childprocess = require("child_process");
function nextTask(msg) {   console.log(msg+"--- Ok ---")            }
function errTask(msg)    {   console.log(msg+"--- Warring! ---")  }
function bash(cmd) {
    console.log("sh "+cmd)
    return new  Promise(  (resolv,reject) => {
          childprocess.exec( cmd, (err, stdout, stderr) =>  err ?  reject(stderr) : resolv(stdout)    ); // pipe stderr and stdout
    })
}
async function multicmd( ) {
    console.log("非同步副程式開始")
    var promise1= bash("ls -al").then(nextTask).catch(errTask)
    console.log("--- Promise1 ---")
    var promise2= bash("pwd").then(nextTask).catch(errTask)
    console.log("--- Promise2 .. ---")
    var promise3= bash("unknow").then(nextTask).catch(errTask);  
    console.log("--- Promise3 .. ---")
    await Promise.all([ promise1, promise2, promise3 ])
    console.log("非同步副程式結束")
}
console.log("主程式開始")
multicmd( ).then(console.log("主程式結束"))

上面 promise1, promise2, promise3 不一定會依照順序輸出結果, async 用意就是將程序包裝成 Promise, 而裡面的程序若有  await 就會暫停等待結果, 一旦結果完成(呼叫 callback function 中的 reject 或 resolve )接著就會繼續往下執行直到非同步程序整個完成才會脫離 :
主程式開始
非同步副程式開始
sh ls -al
--- Promise1 ---
sh pwd
--- Promise2 .. ---
sh unknow
--- Promise3 .. ---
主程式結束
/bin/sh: 1: unknow: not found
--- Warring! ---
/home/mint/work/kotlin
--- Ok ---
total 12
drwxrwxr-x 2 mint mint 4096  9月  6 09:23 .
drwxrwxr-x 4 mint mint 4096  9月  4 13:05 ..
-rw-rw-r-- 1 mint mint  957  9月  6 09:23 a.js
--- Ok ---
非同步副程式結束




沒有留言: