你不知道的JavaScript 同步非同步

weixin_34321977發表於2018-01-16

寫在前面的話

setTimeout是我們學習JavaScript基礎都必須面對的問題,也許當時你搞懂了,但是過一段時間就又忘記了。最近事情不多,我將梳理出for + setTimeout相關的知識點,以及使用Promiseasync/await來加深對非同步、同步的理解

setTimeout

直接進入正題:
for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

也許我們希望輸出的是 1,2,3,4,5,但實際情況是我們輸出了5,5,5,5,5,這到底是為什麼呢?
由JS的執行機制中我們得知:當執行緒中沒有任何同步程式碼的前提下才會執行非同步程式碼。我們的for迴圈時同步的,但是setTimeout是非同步的,由此就造成了輸出5,5,5,5,5這種情況。

通過一頓胡亂分析我們得出假設:只要保證裡面的也變成同步的是不是就可以了呢?

方式①

for(var i = 0; i < 5; i++){
    setTimeout(function(){
        console.log(i);
    }, i*1000)
}

這裡使用的是最簡單的方式:動態的改變延遲的時間。
因為for迴圈時同步的,而setTimeout是非同步的,所以會先把for迴圈執行完畢,然後再執行內部的setTimeout,所以輸出結果為每隔1秒輸出一個5

方式②

for (var i = 0; i < 5; i++) { 
  (function(j){
    setTimeout(function (){
      console.log(j); 
     },1000); 
  })(i); 
}

我們通過設定一個立即執行函式(IIFE),這樣就能保證裡面和外面的是同步執行的。

方式③

function output(i){
    setTimeout(function(){
        console.log(i);
    }, 1000)
}

for(var i = 0; i < 5; i++){
    output(i);
}

這裡其實和第一種方法類似,只不過我們把這個函式單獨拿出來,並把index值當做函式的引數來傳遞

方式④

for(let i = 0; i < 5; i++){
    setTimeout(function(){
        console.log(i);
    }, 1000)
}

letES6語法,for迴圈程式碼塊構成一個作用域,裡面的內容引用了上層作用域的變數 i,並最終形成五個閉包,而for使用var時,還是ES5的寫法,for程式碼塊沒有形成作用域,所以裡面的function不構成閉包。同理我們的方式①方式②都形成了閉包函式。

如果我們需要最後一個延遲5秒,其餘的都是延遲1秒,我們就可以用到ES6的語法--Promise,下面我們用Promise實現這一情況

方式⑤

const task = [];
const output = (i) => new Promise(function(resolve, reject){
    setTimeout(function(){
        console.log(i);
        resolve();
    }, i*1000)
})
for (var i = 0; i<5; i++){
    task.push(output(i));
}
Promise.all(task).then(() => {
    setTimeout(() => {
        console.log(i);
    }, 5000);
})

這裡採用了Promise解決非同步的方式,在ES7中解決非同步還有async/await的方式

方式⑥

const sleep = (timeount) => new Promise((resolve) => {
    setTimeout(resolve, timeount);
});

Func = async () => {
    for(var i = 0; i<5; i++){
        await sleep(1000);
        console.log(i);
    }
    await sleep(5000);
    console.log(i);
}

this.Func();

async 表示這是一個async函式,await只能用在這個函式裡面;
await 表示在這裡等待promise返回結果了,再繼續執行;
await 後面跟著的應該是一個promise物件,否則沒有同步效果

相關文章