【JavaScript】js中同步和異步的區別

同步

同步:只有當前API執行完成後,才能繼續執行下一個API

console.log('before'); 
console.log('after');
//同步API從上到下依次執行,前面代碼會阻塞後面代碼的執行
//輸出結構爲:
//before
//after

異步

異步:當前API的執行不會阻塞後續代碼的執行

console.log('before');
setTimeout(
   () => { console.log('last');
}, 2000);
console.log('after');
//異步則不會等待API執行完成後再向下執行代碼
//輸出爲:
//before
//after
//last

** 爲什麼明明console.log(‘last’);在console.log(‘after’);之前卻先輸出了after?**
這是因爲JavaScript的代碼執行機制引起的

代碼執行規則

在這裏插入圖片描述
代碼運行時,JavaScript維護三個空間分別是 **執行棧 異步任務處理區 任務隊列 **

根據代碼的同步還是異步JavaScript會做出以下操作:

1.先執行,執行棧中的同步任務
2.當遇到異步任務,將其放入異步任務處理區中,而後繼續執行下面的同步任務
3.當異步任務(如綁定的事件或計時器),在異步任務處理區中被觸發JavaScript會按照觸發順序先後將其放入任務隊列中等待被調用
4.一旦執行棧中的所有同步任務執行完畢,系統就會按次序讀取任務隊列中的異步任務,於是被讀取的異步任務結束等待狀態,進入執行棧,開始執行。

注意:執行棧中同步任務執行完後,將任務隊列中的異步任務放入執行棧最下方開始執行

現在我們就知道了爲啥會有同步代碼和異步代碼了

但是同步和異步還有一個非常巨大的區別

獲取返回值

我們都知道同步API可以從返回值中拿到API執行的結果, 但是異步API卻是不可以的

 // 同步
  function sum (n1, n2) { 
      return n1 + n2;
  } 
  const result = sum (10, 20);//30
// 異步
  function getMsg () { 
      setTimeout(function () { 
          return { msg: 'Hello Node.js' }
      }, 2000);
  }
  const msg = getMsg ();
  console.log(msg);//undefined

爲什麼會是undefined?
我們知道JavaScript的代碼執行機制,會將異步代碼放入任務隊列中,而繼續執行下面的同步代碼,而造成undefined的原因正是異步函數還沒有被觸發,所以當調用getMsg ()函數時其中異步函數不阻塞線程,代碼繼續執行,getMsg ()函數默認return了一個undefined

所以這就造成了異步代碼的不可控,我們根本不知道他什麼時候執行,也就無法直接獲取它的執行結果

那有辦法解決麼?
當然

回調函數獲取異步API返回值

我們可以定義一個這樣的函數:

function getData(callback) {
    callback(123);
}
getData(function(n) {
   console.log(n)//輸出123
});

這就是回調函數

通過在函數形參中拿到的回調函數引用地址,可以調用回調函數並傳遞參數給回調函數的形參,這樣回調函數就可以獲得被調用函數中的信息

所以我們剛剛的代碼可以寫成這樣

function getMsg (callback) {
	setTimeout(function () {
		callback({
			msg: 'hello node.js'
		})
	}, 2000)
}

getMsg(function (data) {
	console.log(data);
});

異步代碼執行順序

異步代碼除了返回值還有一個執行順序問題

console.log('代碼開始執行');
setTimeout(() => {
    console.log('2秒後執行的代碼');
}, 2000); 
setTimeout(() => {
    console.log('"0秒"後執行的代碼');
}, 0);
console.log('代碼結束執行');

上面的代碼輸出結果是什麼呢?

我們知道JavaScript的代碼執行機制,來分析一下:
很明顯在執行棧中遇到異步代碼,就會將其放到異步代碼執行區,這時兩個定時器都被放到異步代碼執行區中了,然後在異步代碼執行區中先觸發的異步代碼會先一步被放入任務隊列中,等待調用

顯然輸出結果是先輸出“0秒"後執行的代碼”,然後再輸出’2秒後執行的代碼’

這樣不可控制的輸出順序顯然不是我們期望的,那麼該如何解決呢?

回調函數控制異步代碼執行順序

function getData(callback) {
    setTimeout(() => {
        callback(123);
    }, 0);
}

console.log('代碼開始執行');
getData(function(n) {
    console.log('callback函數被調用了')
    console.log(n)
    console.log('callback函數結束了')
});
console.log('代碼結束執行');
//代碼開始執行
//代碼結束執行
//callback函數被調用了
//123
//callback函數結束了

我們看到在回調函數中代碼是依次進行的

回調函數的作用就是,將異步代碼寫在一個塊級作用域中,當其中的異步函數執行後,纔會調用這個回調函數,這時異步函數已經執行完成,所以在回調函數中代碼就可以如同步代碼一樣依次執行

所以問題就解決了,只需要在回調函數中調用其他的異步API就可以保證異步代碼的順序執行

function getData(callback) {
    setTimeout(function() {
        console.log('2s')
        setTimeout(function() {
            console.log('0s')
            callback(123);
        }, 0)
    }, 2000)


}

console.log('代碼開始執行');
getData(function(n) {
    console.log(n)
});
console.log('代碼結束執行');
//代碼開始執行
//代碼結束執行
//2s
//0s
//123

但這樣做會有一個後果,那就是回調地獄!!

回調地獄

需求:依次讀取A文件、B文件、C文件

const fs = require('fs');

fs.readFile('./A.txt', 'utf8', (err, result1) => {
	console.log(result1)
	fs.readFile('./B.txt', 'utf8', (err, result2) => {
		console.log(result2)
		fs.readFile('./C.txt', 'utf8', (err, result3) => {
			console.log(result3)
		})
	})
});

將依賴當前異步API的執行結果的代碼,寫到所依賴的異步API的回調函數中,逐層嵌套,這樣雖然可以有效解決異步API代碼依賴問題,但這樣寫出來的代碼十分複雜,不可維護。

這樣寫不但繁瑣難以維護,而且寫起來十分的麻煩,所以promise模塊應運而生

promise

爲了解決回調地獄問題,在ES6中提供了Promise

Promise實際上就是在原本的異步API上面包裹一層函數,其中Promise參數函數的resolve , reject兩個參數,實際上和普通的回調函數一樣,都接受一個回調函數作爲實參,而在運行時返回一個實參給調用他的then或catch兩個回調函數,這樣就會獲得異步API中的執行結果

而這個包裹的一層函數就是promise對象

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        if (true) {
            resolve({name: '張三'})
        }else {
            reject('失敗了') 
        } 
    }, 2000);
});
promise.then(result => console.log(result); // {name: '張三'})
       .catch(error => console.log(error); // 失敗了)

而且promise還可以鏈式調用
Promise 的鏈式調用是通過 .then() 和 .catch() 方法實現的,其中 .catch() 等價於 .then(null, onRejected),因此我們在接下來的內容當中只需研究 .then() 方法的性質即可。

.then() 方法除了用於註冊監聽函數之外,本身也會創建並返回一個 Promise 對象,這個 Promise 對象用於表徵回調函數的執行情況。
當回調函數執行成功時(內部的resolve函數調用) Promise 狀態將變更爲 ‘fulfilled’,執行過程拋出異常 (內部的rejected函數調用)Promise 狀態則變成 ‘rejected’

透傳

在鏈式調用過程當中,假如某個環節的 Promise 不存在相應狀態的監聽回調函數,那麼這個 Promise 的狀態將會往下透傳
即,Promise中回調函數執行,卻沒有相應的then()或catch()處理,所以會將上一個 Promise 的狀態持續透傳了下去。

Promise 值傳遞

.then() 方法會將回調函數的執行結果(then中回調函數的return)記錄下來,並作爲下一個 onFulfilled 回調的參數將其傳遞下去
由於回調函數可以返回任何結果,甚至返回一個 Promise 對象也是可行的。
回調函數所拋出的錯誤將作爲下一個 onRejected 的參數傳遞下去

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章