Javascript的同步/非同步機制 (Promise)


你想的同步不是javascript的同步!

最近在開發團隊使用的gitlab客製化系統,透過API的方式取得資訊並呈現,
Javascript + jQuery就成了我們採用的技術。
但因為透過API統計資料,回傳的資料多又得多次呼叫的情況下,
執行效能不佳,每次點下查詢都得等待多時,也因此想將查詢的功能變成非同步執行,
在一進入頁面時,就開始將需要的資料在背景查詢回來,
真正要查詢時,只要將已查回來的資料呈現出來就好了!

非同步是什麼?

非同步的機制就是呼叫某個A功能,但不需等待A的執行結果,
主程序直接往接下來的程式繼續執行。
在Java程式中,大多是以多執行緒的方式來達成,
從主程序產生新的執行緒來執行A功能,獨立於主執行緒之外,
用平行執行來達到非同步的效果。

javascript如何達到非同步?

依序執行程式碼

以往寫程式的時候,我們會很直覺地認為程式會依序往下執行
例如我連續呼叫3個function,程式會依序產出這些結果。
sayHello("Kerwin");
sayHello("John");
sayHello("Kyle");
function sayHello(name) {
  console.log("hi " + name + "!");
}
console output畫面如下:
hi Kerwin!
hi John!
hi Kyle!

調整執行順序

如果我想第二次呼叫sayHello時,先等待1秒再執行,可以使用setTimeout來讓呼叫延遲。
sayHello("Kerwin");
setTimeout(()=>{
 sayHello("John");
}, 1000);  //等待1秒後執行
sayHello("Kyle");
function sayHello(name) {
 console.log("hi " + name + "!");
}
console output畫面如下:
hi Kerwin!
hi Kyle!
hi John!
咦!?  我本來以為程式仍會依序執行,
但實際上卻是先執行了sayHello("Kyle"),才回頭執行sayHello("John")。
這也說明了javascript的特性,在setTimeout的延遲時間執行的function,
並不會讓主程序停下來被等待,而是以非同步的方式繼續執行主程序!
所以...Javascript天生就是「非同步」的嘛!!  XD

Javascript是多執行緒執行嗎?

因為Java的非同步通常是用多執行緒的方式來執行,
所以往往無法保證各執行緒實際執行的順序,
執行緒會隨機地由CPU分配時間去執行指令。
那Javascript呢?   用下列程式執行來觀察setTimeout後的執行順序是否會出現改變?
setTimeout(()=>{sayHello("A");}, 1); //等待1ms後執行
setTimeout(()=>{sayHello("B");}, 1);  //等待1ms後執行
setTimeout(()=>{sayHello("C");}, 1);  //等待1ms後執行
setTimeout(()=>{sayHello("D");}, 1);  //等待1ms後執行
setTimeout(()=>{sayHello("E");}, 1);  //等待1ms後執行
setTimeout(()=>{sayHello("F");}, 1);  //等待1ms後執行
setTimeout(()=>{sayHello("G");}, 1);  //等待1ms後執行
setTimeout(()=>{sayHello("H");}, 1);  //等待1ms後執行
console output畫面如下:
hi A!
hi B!
hi C!
hi D!
hi E!
hi F!
hi G!
hi H!
不管試幾次,順序都不會改變!!
但如果將D改成等待2ms執行
setTimeout(()=>{sayHello("D");}, 2); //等待2ms後執行
那麼hi D!會立刻變成最後執行

由實測結果我們可以推論,Javascript其實是只有單執行緒,
所以在設定一樣的延遲時間之下,主程序呼叫function的順序,會是最後實際執行的順序。

你可以等等我嗎? Javascript怎麼達到同步效果?

話說,有時候我們真的希望執行等待1秒後,再繼續跑後續的程式。
這時就得採用Javascript提供的Promise機制,來達成同步的效果。
function sayHello(name, delay) {
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      console.log("hi " + name + "!");
      resolve(name+"_next", delay-10); //執行完成後,將name加上_next,delay時間減10ms後,當做下一個function的param
    }, delay);
  });
}
sayHello("A",100).then(sayHello).then(sayHello);
console output畫面如下:
hi A!
hi A_next!
hi A_next_next!
照原本非同步的執行方式,因為delay時間愈設愈短,
後面被執行的sayHello應該要較早被執行。
但Promise機制確保了前一個function執行完成(callback)後,
才執行then的下一個function,因而確保了執行的順序。

不過這個Promise是ES 6之後才提供的語法,要先確認執行環境是否有支援唷!
若是不支援的話,可以考慮採用callback的寫法,但語法會較複雜。
可參考下面這篇文章內容:
你懂 JavaScript 嗎?#23 Callback
以上就是Javascript非同步/同步執行機制的分享囉~

留言

這個網誌中的熱門文章