JavaScript非同步程式設計

hankzhuo發表於2017-12-26

一,同步和非同步區別?

相信大家在工作過程中,肯定有或多或少聽說過同步和非同步。那麼同步和非同步到底是什麼呢?它們之間有什麼區別?舉個例子,煮開水,同步就是把水放上去燒,得一直等水開,中途不能做其他事情。而非同步,則是把水放上去燒,讓水在燒,你可以玩手機看電視,等水開了把火關掉。同樣的,程式碼中也是一樣,同步是現在發生的,非同步是未來某個時刻發生的。

同步程式碼:

// 燒開水
function boilWater() {
    var water;
    while(water_is_boiled) {
    // search
    }
    return water;
}

var boiledWater = boilWater();

// 做其他事情
doSomethingElse();
複製程式碼

二,JS 執行機制

在介紹非同步程式設計前,先介紹下JavaScript執行機制,因為JS 是單執行緒執行的,所以這意味著兩段程式碼不能同時執行,而是必須一個接一個地執行,所以,在同步程式碼執行過程中,非同步程式碼是不執行的。只有等同步程式碼執行結束後,非同步程式碼才會被新增到事件佇列中。

(學習1: 阮一峰-Event-Loop)。

三,JS 中非同步有幾種?

JS 中非同步操作還挺多的,常見的分以下幾種:

  • setTimeout (setInterval)
  • AJAX
  • Promise
  • async/await

3.1,setTimeout

setTimeout(
  function() { 
    console.log("Hello!");
}, 1000);
複製程式碼

setTimout(setInterval)並不是立即就執行的,這段程式碼意思是,等 1s後,把這個 function 加入任務佇列中,如果任務佇列中沒有其他任務了,就執行輸出 'Hello'。

var outerScopeVar; 
helloCatAsync(); 
alert(outerScopeVar);

function helloCatAsync() {     
    setTimeout(function() {         
        outerScopeVar = 'hello';     
    }, 2000); 
}
複製程式碼

執行上面程式碼,發現 outerScopeVar 輸出是 undefined,而不是 hello。之所以這樣是因為在非同步程式碼中返回的一個值是不可能給同步流程中使用的,因為 console.log(outerScopeVar) 是同步程式碼,執行完後才會執行 setTimout。

helloCatAsync(function(result) {
console.log(result);
});

function helloCatAsync(callback) {
    setTimeout(
        function() {
            callback('hello')
        }
    , 1000)
}
複製程式碼

把上面程式碼改成,傳遞一個callback,console 輸出就會是 hello。

3.2, AJAX

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 ) {
        console.log(xhr.responseText);
    } else {
        console.log( xhr.status);
    }
}
xhr.open('GET', 'url', false);
xhr.send();
複製程式碼

上面這段程式碼,xhr.open 中第三個引數預設為 false 非同步執行,改為 true 時為同步執行。

3.3,Promise

語法:

new Promise( function(resolve, reject) {...});

Promise 物件是由關鍵字 new 及其建構函式來建立的。這個“處理器函式”接受兩個函式 resolve 和 reject 作為其引數。當非同步任務順利完成且返回結果值時,會呼叫 resolve 函式;而當非同步任務失敗且返回失敗原因(通常是一個錯誤物件)時,會呼叫reject 函式。

new Promise 返回一個 promise 物件,在遇到 resolve 或 reject之前,狀態一直是pending,如果呼叫 resolve 方法,狀態變為 fulfilled,如果呼叫了 reject 方法,狀態變為 rejected。

function delay() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(666)
        }, 2000)
    })
}

delay()
    .then(function(value){
        console.log('resolve...',value);
    })
    .catch(function(err){
        cosole.log(err)
    })
複製程式碼

上面程式碼中,2s 後呼叫 resolve 方法,然後呼叫 then 方法,沒有呼叫 cacth方法。

注意:then() 和 catch() 都是非同步操作。

下面 promise 和 ajax 結合例子:

function ajax(url) {
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 ) {
                resovle(xhr.responseText);
            } else {
                reject( xhr.status);
            }
        }
        xhr.open('GET', url, false);
        xhr.send();
    });
}

ajax('/test.json')
    .then(function(data){
        console.log(data);
    })
    .cacth(function(err){
        console.log(err);
    });
複製程式碼

學習1:promise-mdn

學習2:promise-google

3.4,async/await

語法:

async function name([param[, param[, ... param]]]) { statements }

呼叫 async 函式時會返回一個 promise 物件。當這個 async 函式返回一個值時,Promise 的 resolve 方法將會處理這個值;當 async 函式丟擲異常時,Promise 的 reject 方法將處理這個異常值。

async 函式中可能會有await 表示式,這將會使 async 函式暫停執行,等待 Promise 正常解決後繼續執行 async 函式並返回解決結果(非同步)

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function add1(x) {
  var a = resolveAfter2Seconds(20);
  var b = resolveAfter2Seconds(30);
  return x + await a + await b;
}

add1(10).then(v => {
  console.log(v);  // 2s 後列印 60, 兩個 await 是同時發生的,也就是並行的。
});

async function add2(x) {
  var a = await resolveAfter2Seconds(20);
  var b = await resolveAfter2Seconds(30);
  return x + a + b;
}

add2(10).then(v => {
  console.log(v);  // 4s 後列印 60,按順序完成的。
});
複製程式碼

上面程式碼是 mdn 的一個例子。

  1. 說明了 async 返回的是一個 promise物件;
  2. await 後面跟的表示式是一個 promise,執行到 await時,函式暫停執行,直到該 promise 返回結果,並且暫停但不會阻塞主執行緒。
  3. await 的任何內容都通過 Promise.resolve() 傳遞。
  4. await 可以是並行(同時發生)和按順序執行的。

看下面兩段程式碼:

async function series() {
    await wait(500);
    await wait(500);
    return "done!";
}

async function parallel() {
  const wait1 = wait(500);
  const wait2 = wait(500);
  await wait1;
  await wait2;
  return "done!";
}
複製程式碼

第一段程式碼執行完畢需要 1000毫秒,這段 await 程式碼是按順序執行的;第二段程式碼執行完畢只需要 500 毫秒,這段 await 程式碼是並行的。

學習1:async-mdn

學習2:async-google

四,思考題

下面程式碼輸出順序是什麼?

async function fn1() {
    console.log(1);
    await fn2();
    console.log(3);
};

function fn2() {
    console.log(2);
};

fn1();
new Promise(function(resolve, reject) {
    console.log(4);
    resolve();
}).then(function(){
    console.log(5)
})
複製程式碼

原文地址,這裡有很多優秀文章。

相關文章