【學習筆記】JS經典異步操做,從閉包到async/await

參考文獻:王仕軍——知乎專欄前端週刊html

感謝做者的熱心總結,本文在理解的基礎上,根據本身能力水平做了一點小小的修改,在加深本身印象的同時也但願能和各位共同進步...前端

 1. 異步與for循環

拋出一個問題,下面的代碼輸出什麼?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

2. 閉包

如今咱們把需求改一下,但願輸出的結果是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傳了一個參數,因此它就會保存每次循環的實參,因此獲得的結果和採用當即執行函數的結果一致。模塊化

3. ES6語法

固然咱們也可使用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);
})

4. async/await優化

上次寫了一篇關於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);
})();
相關文章
相關標籤/搜索