參考文獻:王仕軍——知乎專欄前端週刊html
感謝做者的熱心總結,本文在理解的基礎上,根據本身能力水平做了一點小小的修改,在加深本身印象的同時也但願能和各位共同進步...前端
拋出一個問題,下面的代碼輸出什麼?java
1 for (var i = 0; i < 5; i++) { 2 setTimeout(function() { 3 console.log(i); 4 }, 1000); 5 } 6 console.log(i);
相信絕大部分同窗都能答的上,它的正確答案是當即輸出5,過1秒鐘後一次性輸出5個5,這是一個典型的JS異步問題,首先for循環的循環體是一個異步函數,而且變量i添加到全局環境中,因此當即輸出一個5,一秒鐘後,異步函數setTimeout輸出五次循環的結果,打印5 5 5 5 5(沒有時間間隔)。promise
如今咱們把需求改一下,但願輸出的結果是5 ->0,1,2,3,4, 應該怎麼修改代碼呢?閉包
很明顯咱們能夠用閉包建立一個不銷燬的做用域,保證變量i每次都能正常輸出。 異步
1 for(var i=0;i<5;i++){ 2 (function(j) 3 {setTimeout(() => { 4 console.log(j); //過一秒輸出 0,1,2,3,4 5 }, 1000)})(i) 6 } 7 console.log(i); //當即輸出5
由於當即執行會形成內存泄漏不創建大量使用,那麼咱們還能夠這樣async
var output = function(i){ setTimeout(()=>{ console.log(i); // 過1秒輸出0,1,2,3,4 },1000) } for(var i=0;i<5;i++){ output(i); } console.log(i); //當即輸出5
JS基本類型是按值傳遞的,咱們給函數output傳了一個參數,因此它就會保存每次循環的實參,因此獲得的結果和採用當即執行函數的結果一致。模塊化
固然咱們也可使用ES6的語法,還記得for循環中使用let聲明能夠有效阻止變量添加到全局做用域嗎?函數
1 for(let i=0;i<5;i++){ 2 setTimeout(()=>{ 3 console.log(i) //一秒鐘後同時輸出0,1,2,3,4 4 },1000) 5 }
6 console.log(i) //這一行會報錯,由於i只存在於for循環中
for循環中let聲明有一個特色,i只在本輪循環中有效,因此每循環一個i其實都是新變量,而javaScript引擎內部會記住上一次循環的值,初始化變量i時,就在上輪循環基礎上計算。優化
如今咱們又改一下需求,但願先輸出0,以後每隔一秒依次輸出1,2,3,4,循環結束再輸出5。
很容易想到,咱們能夠再增長一個定時器,定時器的時間和循環次數有關
1 for(var i=0;i<5;i++){ 2 (function(j){ 3 setTimeout(() => { 4 console.log(j) //當即輸出0,以後每隔1秒輸出1,2,3,4 5 }, 1000*j); 6 })(i) 7 } 8 setTimeout(()=>{ 9 console.log(i) //循環結束輸出5 10 },1000*i)
這雖然也是個辦法,但代碼寫着確實不太好看,異步操做咱們首先就要想到Promise對象,嘗試用Promise對象來改寫
let tasks = []; for(var i=0;i<5;i++){ ((j)=>{ tasks.push(new Promise( (resolve)=>{ setTimeout(() => { console.log(j); resolve(); //執行resolve,返回Promise處理結果 }, 1000*j); } )) })(i) } Promise.all(tasks).then(()=>{ setTimeout(() => { console.log(i); }, 1000); //只要把時間設爲1秒 })
Promise.all返回一個Promise實例,在tasks的promise狀態爲resolved時回調完成,這就是咱們必需要在循環體中resolve()的緣由。
咱們將上面的代碼從新排版,讓其顆粒度更小,模塊化更好,簡潔明瞭
let tasks = []; //存放一個異步操做 let output = (i)=> //返回一個Promise對象 new Promise((resolve)=>{ setTimeout(() => { console.log(i); resolve(); }, 1000*i); }) for(var i=0;i<5;i++){ //生成所有的異步操做 tasks.push(output(i)) } Promise.all(tasks).then(()=>{ //tasks裏的promise對象都爲resolved調用then鏈的第一個回調函數 setTimeout(() => { console.log(i) }, 1000); })
上次寫了一篇關於async和await優化then鏈的博客,感興趣的能夠看看:深刻理解async/await
對於then鏈,咱們是能夠進一步優化的:
let sleep = (timeountMS) => new Promise((resolve) => { setTimeout(resolve, timeountMS); }); (async () => { // 聲明即執行的 async 函數表達式 for (var i = 0; i < 5; i++) { await sleep(1000); console.log(i); } await sleep(1000); console.log(i); })();