之前在項目中遇到過好多次因為異步引起的變量沒有值,所以意識到了認識js中同步與異步機制的重要性
在單線程的js中,異步代碼會被放入一個事件隊列,等到所有其他代碼執行后再執行,而不會阻塞線程。
下面是js幾種最常見的異步情況:
- 異步函數 setTimeout和setInterval
異步函數,如setTimeout和setInterval,被壓入了稱之為Event Loop的隊列。
setTimeout:在指定的毫秒數后,將定時任務處理的函數添加到執行隊列的隊尾。所以有時候也可以使用setTimeout解決異步帶來的問題
setInterval:按照指定的周期(以毫秒數計時),將定時任務處理函數添加到執行隊列的隊尾。
Event Loop是一個回調函數隊列。當異步函數執行時,回調函數會被壓入這個隊列。JavaScript引擎直到異步函數執行完成后,才會開始處理事件循環。這意味着JavaScript代碼不是多線程的,即使表現的行為相似。事件循環是一個先進先出(FIFO)隊列,這說明回調是按照它們被加入隊列的順序執行的。 - ajax
- node.js中的許多函數也是異步的
解決由的js異步引起的問題辦法:
- 命名函數
清除嵌套回調的一個便捷的解決方案是簡單的避免雙層以上的嵌套。傳遞一個命名函數給作為回調參數,而不是傳遞匿名函數
例:1 var fromLatLng, toLatLng; 2 var routeDone = function( e ){ 3 console.log( "ANNNND FINALLY here's the directions..." ); 4 // do something with e 5 }; 6 var toAddressDone = function( results, status ) { 7 if ( status == "OK" ) { 8 toLatLng = results[0].geometry.location; 9 map.getRoutes({ 10 origin: [ fromLatLng.lat(), fromLatLng.lng() ], 11 destination: [ toLatLng.lat(), toLatLng.lng() ], 12 travelMode: "driving", 13 unitSystem: "imperial", 14 callback: routeDone 15 }); 16 } 17 }; 18 var fromAddressDone = function( results, status ) { 19 if ( status == "OK" ) { 20 fromLatLng = results[0].geometry.location; 21 GMaps.geocode({ 22 address: toAddress, 23 callback: toAddressDone 24 }); 25 } 26 }; 27 GMaps.geocode({ 28 address: fromAddress, 29 callback: fromAddressDone 30 });
async.js 庫可以幫助我們處理多重Ajax requests/responses,如:
1 async.parallel([ 2 function( done ) { 3 GMaps.geocode({ 4 address: toAddress, 5 callback: function( result ) { 6 done( null, result ); 7 } 8 }); 9 }, 10 function( done ) { 11 GMaps.geocode({ 12 address: fromAddress, 13 callback: function( result ) { 14 done( null, result ); 15 } 16 }); 17 } 18 ], function( errors, results ) { 19 getRoute( results[0], results[1] ); 20 });
- 使用promise
promise在異步執行的流程中,把執行代碼和處理結果的代碼清晰地分離了:promise還可以做若干個異步的任務,例:有一個異步任務,需要先做任務1,如果任務成功后再做任務2,任何任務失敗則不再繼續並執行錯誤處理函數。
例:job1.then(job2).then(job3).catch(handleError); //
job1
、job2
和job3
都是Promise對象
1 'use strict'; 2 3 var logging = document.getElementById('test-promise2-log'); 4 while (logging.children.length > 1) { 5 logging.removeChild(logging.children[logging.children.length - 1]); 6 } 7 8 function log(s) { 9 var p = document.createElement('p'); 10 p.innerHTML = s; 11 logging.appendChild(p); 12 } 13 // 0.5秒后返回input*input的計算結果: 14 function multiply(input) { 15 return new Promise(function (resolve, reject) { 16 log('calculating ' + input + ' x ' + input + '...'); 17 setTimeout(resolve, 500, input * input); 18 }); 19 } 20 21 // 0.5秒后返回input+input的計算結果: 22 function add(input) { 23 return new Promise(function (resolve, reject) { 24 log('calculating ' + input + ' + ' + input + '...'); 25 setTimeout(resolve, 500, input + input); 26 }); 27 } 28 29 var p = new Promise(function (resolve, reject) { 30 log('start new Promise...'); 31 resolve(123); 32 }); 33 34 p.then(multiply) 35 .then(add) 36 .then(multiply) 37 .then(add) 38 .then(function (result) { 39 log('Got value: ' + result); 40 });
關於promise的兩個方法
1 var p1 = new Promise(function (resolve, reject) { 2 setTimeout(resolve, 500, 'P1'); 3 }); 4 var p2 = new Promise(function (resolve, reject) { 5 setTimeout(resolve, 600, 'P2'); 6 }); 7 // 同時執行p1和p2,並在它們都完成后執行then: 8 Promise.all([p1, p2]).then(function (results) { 9 console.log(results); // 獲得一個Array: ['P1', 'P2'] 10 });
1 var p1 = new Promise(function (resolve, reject) { 2 setTimeout(resolve, 500, 'P1'); 3 }); 4 var p2 = new Promise(function (resolve, reject) { 5 setTimeout(resolve, 600, 'P2'); 6 }); 7 Promise.race([p1, p2]).then(function (result) { 8 console.log(result); // 'P1' 9 });
//由於p1
執行較快,Promise的then()
將獲得結果'P1'
。p2
仍在繼續執行,但執行結果將被丟棄。
本文參考: