【js】同步和非同步知識總結
阿新 • • 發佈:2020-10-15
- 同步API
- 連續的執行,就叫做同步
- 連續執行,不能插入其他任務,所以作業系統從硬碟讀取檔案的這段時間,程式只能乾等著。
- 同步API:只有當前API執行完成後,才能繼續執行下一個API
- 前一個任務結束後再執行後一個任務,程式的執行順序與任務的排列順序是一致的、同步的。
- 同步任務都在主執行緒上執行,形成一個執行棧。
- 連續的執行,就叫做同步
- 非同步API
- 所謂"非同步",簡單說就是一個任務分成兩段,先執行第一段,然後轉而執行其他任務,等做好了準備,再回過頭執行第二段。
- 比如,有一個任務是讀取檔案進行處理,非同步的執行過程就是下面這樣。
- 任務的第一段是向作業系統發出請求,要求讀取檔案。
- 然後,程式執行其他任務,等到作業系統返回檔案,再接著執行任務的第二段(處理檔案)
- 這種不連續的執行,就叫做非同步。
- 非同步API:當前API的執行不會阻塞後續程式碼的執行
- JS 的非同步是通過回撥函式實現的。
- 非同步任務相關回調函式新增到任務佇列中(任務佇列也稱為訊息佇列)。
- 一般而言,非同步任務有以下三種類型:
- 1、普通事件,如click、resize等
- 2、資源載入,如load、error等
- 3、定時器,包括setinterval、setTimeout等
- js執行機制
- 1.先執行,執行棧中的同步任務
- 當遇到非同步任務,將其放入回撥函式佇列中,繼續執行下面的同步任務
- 2.非同步任務(回撥函式),非同步程序處理,將其放入回撥函式佇列中
- 3.一旦執行棧中的所有同步任務執行完畢,系統就會按次序讀取回調函式佇列中的非同步任務,於是被讀取的非同步任務結束等待狀態,進入執行棧,開始執行。
- 執行棧中同步任務執行完後,將回調函式佇列中的非同步任務放入,執行棧最下方開始執行
- 非同步程序處理
- 在主執行緒任務棧,遇到非同步任務,會將其提交給非同步程式碼執行區,當非同步任務被觸發後,非同步程序處理會將其提交個回撥函式佇列
- 當主執行緒任務棧中同步任務執行完後,將回調函式佇列中的非同步任務放入,執行棧最下方開始執行
- 當將放入執行棧的非同步任務也執行完後,主執行緒還會去任務佇列檢視是否還有任務可執行
- 事件迴圈
- 由於主執行緒不斷的重複獲得任務、執行任務、再獲取任務、再執行,所以這種機制被稱為事件迴圈(eventloop)。
- 1.先執行,執行棧中的同步任務
- 回撥函式
- JavaScript 語言對非同步程式設計的實現,就是回撥函式。
- 所謂回撥函式,就是把任務的第二段單獨寫在一個函式裡面,等到重新執行這個任務的時候,就直接呼叫這個函式。
- 比如:讀取檔案進行處理,是這樣寫的
- readFile 函式的第二個引數,就是回撥函式,也就是任務的第二段。
- 等到作業系統返回了 /etc/passwd 這個檔案以後,回撥函式才會執行。
- 回撥函式的作用就是,將非同步程式碼寫在一個塊級作用域中,當其中的非同步函式執行後,才會呼叫回撥函式,這時非同步函式已經執行完成,所以呼叫的回撥函式中程式碼可以依次執行
- 通過在函式形參中拿到的回撥函式引用地址,可以呼叫回撥函式並傳遞引數給回撥函式的形參,這樣回撥函式就可以獲得被呼叫函式中的資訊
- Promise
- 回撥地獄
- 回撥函式本身並沒有問題,它的問題出現在多個回撥函式巢狀
- 比如:假定讀取A檔案之後,再讀取B檔案,程式碼如下。
- 不難想象,如果依次讀取多個檔案,就會出現多重巢狀。程式碼不是縱向發展,而是橫向發展,很快就會亂成一團,無法管理。這種情況就稱為“回撥地獄”(callback hell)。
- Promise就是為了解決這個問題而提出的。
- 在ES6中提供了Promise,它不是新的語法功能,而是一種新的寫法,允許將回調函式的橫向載入,改成縱向載入。
- 語法
- Promise實際上是一個建構函式,所以需要先使用new關鍵字建立例項
- 引數
- Promise的建構函式接收一個函式的引數,此函式有兩個引數:resolve , reject,分別表示非同步操作執行成功和非同步執行失敗的後的回撥函式。
- resolve
- resolve是將Promise的狀態置為fullfiled,fullfiled狀態將觸發then方法
- 非同步操作執行成功可以使用resolve帶回一個實參
- resolve實際上是一個回撥函式,可以將資料作為實參傳遞出非同步API
- Promise物件呼叫then方法,then接收一個函式引數,並且會拿到Promise執行成功回撥resolve函式的引數。
- 就相當於回撥函式帶回非同步API中資料
- reject
- reject是將Promise的狀態置為rejected,rejected狀態將觸發catch方法
- 非同步操作執行失敗可以使用reject帶回一個實參
- reject實際上是一個回撥函式,可以將資料作為實參傳遞出非同步API
- Promise物件呼叫catch方法,catch接收一個函式引數,並且會拿到Promise執行成功回撥 reject函式的引數。
- 就相當於回撥函式帶回非同步API中資料
- resolve
- Promise的建構函式接收一個函式的引數,此函式有兩個引數:resolve , reject,分別表示非同步操作執行成功和非同步執行失敗的後的回撥函式。
- Promise方法
- Promise 提供了 .then(onFulfilled, onRejected) 和 .catch(onRejected) 方法用於註冊監聽 Promise 物件的狀態變更。
- Promise物件呼叫then方法,then接收一個函式引數,並且會拿到Promise執行成功回撥resolve函式的引數。
- then()方法是非同步執行。
- 意思是:就是當.then()前的方法執行完後再執行then()內部的程式,這樣就避免了,資料沒獲取到等的問題。
- 語法
- promise.then([onFulfilled][, onRejected])
- 其中onFulfilled,就是resolve狀態的回撥函式,用於指定發生錯誤時的回撥函式
- onRejected就是reject
- 就相當於回撥函式帶回非同步API中資料
- 引數
- 1.如果引數是 Promise 例項,那麼 promise.resolve 將不做任何修改、原封不動地返回這個例項。
- 2.如果引數是一個 thenable 物件。
- thenable 物件指的是具有 then 方法的物件
- 比如:
- Promise.resolve 方法會將這個物件轉為 Promise 物件,然後就立即執行thenable物件的then方法。
- 3.如果引數是一個原始值,或者是一個不具有then方法的物件,則 Promise.resolve方法返回一個新的 Promise 物件,狀態為resolved。
- 比如:下面程式碼生成一個新的 Promise 物件的例項p。
- 由於字串Hello不屬於非同步操作(判斷方法是字串物件不具有 then 方法),返回 Promise 例項的狀態從一生成就是resolved,所以回撥函式會立即執行。
- Promise.resolve方法的引數,會同時傳給回撥函式。
- 比如:下面程式碼生成一個新的 Promise 物件的例項p。
- 4.Promise.resolve 方法允許呼叫時不帶引數,直接返回一個 resolved 狀態的 Promise 物件。
- 需要注意的是,立即 resolve() 的 Promise 物件,是在本輪“事件迴圈”(event loop)的結束時執行,而不是在下一輪“事件迴圈”的開始時。
- 返回值
- 返回了一個值,那麼then返回的 Promise 將會成為接受狀態,並且將返回的值作為接受狀態的回撥函式的引數值。
- 沒有返回任何值,那麼then返回的 Promise 將會成為接受狀態,並且該接受狀態的回撥函式的引數值為undefined。
- 返回一個已經是接受狀態的 Promise,那麼then返回的 Promise 也會成為接受狀態,並且將那個 Promise 的接受狀態的回撥函式的引數值作為該被返回的Promise的接受狀態回撥函式的引數值。
- 返回一個已經是拒絕狀態的 Promise,那麼then返回的 Promise 也會成為拒絕狀態,並且將那個 Promise 的拒絕狀態的回撥函式的引數值作為該被返回的Promise的拒絕狀態回撥函式的引數值。
- 返回一個未定狀態(pending)的 Promise,那麼then返回 Promise 的狀態也是未定的,並且它的終態與那個 Promise 的終態相同;同時,它變為終態時呼叫的回撥函式引數與那個 Promise 變為終態時的回撥函式的引數是相同的。
- Promise物件呼叫catch方法,catch接收一個函式引數,並且會拿到Promise執行成功回撥 reject函式的引數。
- .catch() 等價於 .then(null, onRejected),用於指定發生錯誤時的回撥函式
- 語法
- promise.catch(error => console.log(error); // 失敗了
- 就相當於回撥函式帶回非同步API中資料
- Promise實際上就是在原本的非同步API中包裹一層函式,其中Promise引數函式的resolve , reject兩個引數,實際上和普通的回撥函式一樣,都接受一個回撥函式作為實參,而在執行時返回一個實參給呼叫他的then或catch方法,這樣就會獲得非同步API中的執行結果
- 我們可以把任意一個非同步操作過程包裹進 Promise 的回撥函式體裡面,當非同步操作成功或失敗時分別呼叫 resolve(value) 或 reject(error) 變更 Promise 物件的狀態,這樣就成功地把任意形式的非同步操作狀態轉換為格式統一的 Promise 狀態。
- 例項化的 promise 物件維護著一個狀態,resolve() 和 reject() 則是用於觸發 promise 變更的開關。
- Promise能把原來的回撥寫法分離出來,在非同步操作執行完後,用鏈式呼叫的方式執行回撥函式。
- Promise相對於普通的回撥函式(callback)來說從從表面上來說可以簡化層層回撥的寫法,Promise的精髓是“狀態”,用維護狀態、傳遞狀態的方式來使得回撥函式能夠及時呼叫,它比傳遞callback函式要簡單、靈活的多。
- 控制Promise的執行順序
- 使用Promise雖然可以將非同步API的執行結果傳遞出來,但是無法控制非同步API的執行順序
- 只是new了一個物件,並沒有呼叫它,我們傳進去的函式就已經執行了
- 所以我們用Promise的時候一般是包在一個函式中,在需要的時候去執行這個函式
- 在Promise的外面再巢狀一層函式,就可以實現控制Promise的執行
- Promise的鏈式程式設計
- Promise只是能夠簡化層層回撥的寫法,而實質上,Promise的精髓是“狀態”,用維護狀態、傳遞狀態的方式來使得回撥函式能夠及時呼叫,它比傳遞callback函式要簡單、靈活的多。
- Promise 的鏈式呼叫是通過 .then() 和 .catch() 方法實現的,其中 .catch() 等價於 .then(null, onRejected),因此我們在接下來的內容當中只需研究 .then() 方法的性質即可。
- .then() 方法除了用於註冊監聽函式之外,本身也會建立並返回一個 Promise 物件,這個 Promise 物件用於表徵回撥函式的執行情況。
- 當回撥函式執行成功時(內部的resolve函式呼叫) Promise 狀態將變更為 'fulfilled',執行過程丟擲異常 (內部的rejected函式呼叫)Promise 狀態則變成 'rejected'
- 透傳
- 在鏈式呼叫過程當中,假如某個環節的 Promise 不存在相應狀態的監聽回撥函式,那麼這個 Promise 的狀態將會往下透傳
- .then 或者 .catch 的引數期望是函式,傳入非函式則會發生值穿透
- 即,Promise中回撥函式執行,卻沒有相應的then()或catch()處理,所以會將上一個 Promise 的狀態持續透傳了下去。
- 在鏈式呼叫過程當中,假如某個環節的 Promise 不存在相應狀態的監聽回撥函式,那麼這個 Promise 的狀態將會往下透傳
- Promise 值傳遞
- .then() 方法會將回調函式的執行結果(then中回撥函式的return)記錄下來,並作為下一個 onFulfilled (resolve成功狀態)回撥的引數將其傳遞下去
- 由於回撥函式可以返回任何結果,甚至返回一個 Promise 物件也是可行的。
- 回撥函式所丟擲的錯誤將作為下一個 onRejected 的引數傳遞下去
- .then() 方法會將回調函式的執行結果(then中回撥函式的return)記錄下來,並作為下一個 onFulfilled (resolve成功狀態)回撥的引數將其傳遞下去
- 回撥地獄
- Generator函式
- 協程思想
- 傳統的程式語言,早有非同步程式設計的解決方案(其實是多工的解決方案),其中有一種叫做“攜程”(coroutine),意思是多個執行緒互相協作,完成非同步任務。
- 協程有點像函式,又有點像執行緒。它的執行流程大致如下:
- 第一步,協程A開始執行。
- 第二步,協程A執行到一半,進入暫停,執行權轉移到協程B。
- 第三步,(一段時間後)協程B交還執行權。
- 第四步,協程A恢復執行。
- 上面流程的協程A,就是非同步任務,因為它分成兩段(或多段)執行。
- 舉例來說,讀取檔案的協程寫法如下:
- 上面程式碼的函式 asyncJob 是一個協程,它的奧妙就在其中的 yield 命令。
- 它表示執行到此處,執行權將交給其他協程,也就是說,yield命令是非同步兩個階段的分界線。
- 協程遇到 yield 命令就暫停,等到執行權返回,再從暫停的地方繼續往後執行,它的最大優點,就是程式碼的寫法非常像同步操作,如果去除yield命令,簡直一模一樣。
- 概念
- Generator 函式是協程在 ES6 的實現,最大特點就是可以交出函式的執行權(即暫停執行)
- 下面程式碼就是一個 Generator 函式。它不同於普通函式,是可以暫停執行的,所以函式名之前要加星號,以示區別。
- 語法
- Generator(生成器)是一類特殊的函式,跟普通函式宣告時的區別是function後加了一個*號
- 整個 Generator 函式就是一個封裝的非同步任務,或者說是非同步任務的容器,非同步操作需要暫停的地方,都用yield 語句註明
- 返回值
- 呼叫 Generator 函式,會返回一個內部指標(即遍歷器),即執行它不會返回結果,返回的是指標物件
- 上面程式碼中,呼叫 Generator 函式,會返回一個內部指標(遍歷器)g
- 呼叫指標 的next 方法,會移動內部指標(即執行非同步任務的下一個yield 語句),指向下一個遇到的 yield 語句
- next 方法的作用是分階段執行 Generator 函式
- 每次呼叫 next 方法,會返回一個物件,表示當前階段的資訊( value 屬性和 done 屬性)。
- value 屬性是 yield 語句後面表示式的值,表示當前階段的值;
- done 屬性是一個布林值,表示 Generator 函式是否執行完畢,即是否還有下一個階段。
- next()會比yield多了一個
- 進行例項化之後,Generator函式裡的程式碼不會主動執行。
- 第一個next()永遠是用於啟動生成器,生成器啟動後要想執行到最後,其內部的每個yield都會對應一個next(),所以說next()永遠都會比yield多一個了
- 呼叫 Generator 函式,會返回一個內部指標(即遍歷器),即執行它不會返回結果,返回的是指標物件
- 訊息傳遞
- 通過yield ...和next(...)組合使用,可以在生成器的執行過程中構成一個雙向訊息傳遞系統。
- next 方法可以接受引數,這是向 Generator 函式體內輸入資料
- 第一個 next 方法的 value 屬性,返回表示式 x + 2 的值(3)。
- 第二個 next 方法帶有引數2,這個引數可以傳入 Generator 函式,作為上個階段非同步任務的返回結果,被函式體內的變數 y 接收。
- 當next(..)執行到yield語句處時會暫停生成器的執行,同時next(...)會得到一個帶有value屬性的物件,yield語句後面帶的值會賦給value(如果yield後面沒有值,value就為undefined)。可以將yield ...效果看成跟return ...類似。
- 當生成器處於暫停狀態時,暫停的yield表示式處可以接收下一個啟動它的next(...)傳進來的值。當next(...)使生成器繼續往下執行時,其傳入的值會將原來的yield語句替換掉。
- 異常處理
- Generator 函式內部還可以部署錯誤處理程式碼,捕獲函式體外丟擲的錯誤
- 使用指標物件的 throw 方法丟擲的錯誤,可以被函式體內的 try ... catch 程式碼塊捕獲
- 上面程式碼的最後一行,Generator 函式體外,使用指標物件的 throw 方法丟擲的錯誤,可以被函式體內的 try ... catch 程式碼塊捕獲
- 協程思想
- Generator+Promise實現完美非同步
- 例子:
- 當多個Promise併發請求時,正確的寫法可以更好地提高效能
- 請求1和2之間不存在依賴關係,是可以同時進行的
- 非同步函式(async和await)
- ES7中新增加了非同步函式的部分
- 優化了promise中臃腫冗餘的部分封裝成API
- 非同步函式是非同步程式設計語法的終極解決方案,它可以讓我們將非同步程式碼寫成同步的形式,讓程式碼不再有回撥函式巢狀,使程式碼變得清晰明瞭
- 所謂非同步函式,實際上就是在普通函式定義的前面新增async關鍵字,這樣普通函式就會變成非同步函式
- 語法
- 在普通函式定義的前面新增async關鍵字,將其變成非同步函式
- 返回值
- 普通函式預設的返回值是undefined,而非同步函式的預設返回值是在原有返回值的基礎上,包裹了promise物件
- 在非同步函式內部使用return關鍵字進行結果返回 結果會被包裹的promise物件中return關鍵字代替了resolve方法
- 非同步函式返回promise物件可以使用then方法
- 呼叫非同步函式再鏈式呼叫then方法獲取非同步函式執行結果
- 非同步函式呼叫then方法,then中的回撥函式引數就是非同步函式的返回值
- 丟擲異常 throw
- 在非同步函式內部使用throw關鍵字丟擲程式異常
- 在非同步函式中使用throw關鍵字,可以丟擲異常,將錯誤資訊丟擲到非同步函式外
- 非同步函式返回promise物件可以使用catch方法
- 呼叫非同步函式再鏈式呼叫catch方法獲取非同步函式執行的錯誤資訊
- 非同步函式呼叫catch方法,catch中的回撥函式引數就是非同步函式中丟擲異常的資訊
- throw關鍵字會導致非同步函式暫停,不在執行後續程式碼
- await關鍵字
- await關鍵字將會拿到promise物件中傳遞出來的回撥引數
- await關鍵字只能出現在非同步函式中,也就是說,當使用await獲取結果的時候可能會出現被迫多次寫async的情況
- await promise
- await後面只能寫promise物件寫其他型別的API是不可以的
- 使用await關鍵字可以讓promise物件脫離(起到呼叫promise物件then方法作用)
- await 可以暫停非同步函式的執行,等待promise物件返回結果後再向下執行後續結果
- 也就是說,使用await關鍵字可以有序的讓非同步函式執行
- promisify方法
- 只有非同步函式才能使用await,且await必須跟在promise物件前,而原有API並沒有返回promise物件
- promisify方法可以包裝現有的非同步API函式,將其變為非同步函式
- promisify方法存放在系統模組,util中
- 語法
- promisify(要改造的API函式)
- 注意這裡是改造,不是呼叫,所以引數應該為引用
- 返回值
- promisify返回處理好的API函式,即一個返回promise物件的非同步函式