JS中的異步解決方案

JS中異步處理方案

本章介紹三種異步處理方案:ios

  • 回調函數(callback)
  • promise
  • async/await

若是想要深刻了解 promisegenerator+coasync/await 的話,建議參考個人這篇文章《深刻理解 promise、generator+co、async/await 用法》git

回調函數(callback)

回調函數應該屬於最簡單粗暴的一種方式,主要表現爲在異步函數中將一個函數進行參數傳入,當異步執行完成以後執行該函數編程

話很少說,上代碼:axios

// 有三個任務console.log(1)console.log(2)console.log(3)
// 過5s執行任務1,任務1執行完後,再過5s執行任務2.....
window.setTimeout(function(){
	console.log(1)
	window.setTimeout(function(){
		console.log(2)
		window.setTimeout(function(){
			console.log(3)
		},5000)
	},5000)
},5000)
複製代碼

看出這種方式的缺點了嗎?沒錯,試想,若是再多幾個異步函數,代碼總體的維護性,可讀性都變的極差,若是出了bug,修復的排查過程也變的極爲困難,這個即是所謂的 回調函數地獄promise

promise

promise簡單的說就是一個容器,裏面保存着某個將來纔會結束的時間(一般是一個異步操做)的結果。從語法上說,promise就是一個對象,從它能夠獲取異步操做的消息。promise提供統一的API,各類異步操做均可以用一樣的方法處理。bash

如何理解:異步

  • 沒有異步就不須要promise
  • promise自己不是異步,只是咱們去編寫異步代碼的一種方式

promise有所謂的 4 3 2 1async

4大術語
必定要結合異步操做來理解
既然是異步,這個操做須要有個等待的過程,從操做開始,到獲取結果,有一個過程的異步編程

  • 解決(fulfill)指一個 promise 成功時進行的一系列操做,如狀態的改變、回調的執行。雖然規範中用 fulfill 來表示解決,但在後世的 promise 實現多以 resolve 來指代之
  • 拒絕(reject)指一個 promise 失敗時進行的一系列操做
  • 終值(eventual value)所謂終值,指的是 promise 被解決時傳遞給解決回調的值,因爲 promise 有一次性的特徵,所以當這個值被傳遞時,標誌着 promise 等待態的結束,故稱之終值,有時也直接簡稱爲值(value)
  • 據因(reason)也就是拒絕緣由,指在 promise 被拒絕時傳遞給拒絕回調的值

3種狀態
在異步操做中,當操做發出時,須要處於等待狀態
當操做完成時,就有相應的結果,結果有兩種:函數

  • 成功了
  • 失敗了

一共是3種狀態,以下:

  • 等待態(Pending (也叫進行態)
  • 執行態(Fulfilled)(也叫成功態)
  • 拒絕態(Rejected) (也叫失敗態)

圖片加載失敗

針對每一種狀態,有一些規範:

等待態(Pending)
處於等待態時,promise 需知足如下條件:

  • 能夠遷移至執行態或拒絕態

執行態(Fulfilled)
處於執行態時,promise 需知足如下條件:

  • 不能遷移至其餘任何狀態
  • 必須擁有一個不可變的終值

拒絕態(Rejected)
處於拒絕態時,promise 需知足如下條件:

  • 不能遷移至其餘任何狀態
  • 必須擁有一個不可變的據因

2種事件
針對3種狀態,只有以下兩種轉換方向:

  • pending –> fulfilled
  • pendeing –> rejected

在狀態轉換的時候,就會觸發事件:

  • 若是是pending –> fulfiied,就會觸發onFulFilled事件
  • 若是是pendeing –> rejected,就會觸發onRejected事件

在調用resolve方法或者reject方法的時候,就必定會觸發事件

須要註冊onFulFilled事件 和 onRejected事件
針對事件的註冊,Promise對象提供了then方法,以下:
promise.then(onFulFilled,onRejected)

針對 onFulFilled,會自動提供一個參數,做爲終值(value)
針對 onRejected,會自動提供一個參數,做爲據因(reason)

1個對象
promise

注:只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘的操做都沒法改變這個狀態

簡單來說,就仍是promise中有着三種狀態pending,fulfilled,rejected。在代碼中咱們能夠控制狀態的變動

new Promise(function(resolve,reject){
	console.log("pending");
	console.log("pending");
	resolve();
	reject();
})
複製代碼

建立一個Promise對象須要傳入一個函數,函數的參數是resolve和reject,在函數內部調用時,就分別表明狀態由pending=>fulfilled(成功),pending=>rejected(失敗)

一旦promise狀態發生變化以後,以後狀態就不會再變了。好比:調用resolve以後,狀態就變爲fulfilled,以後再調用reject,狀態也不會變化

在建立promise對象,只須要根據需求,轉換狀態便可。無非就是調用兩個函數:

  • resolve,傳遞value
  • reject,傳遞reason

Promise對象在建立以後會馬上執行,所以通常的作法是使用一個函數進行包裝,而後return一個promise對象

function betray(){
	return new Promise(function(resolve,reject){
		...//異步操做
	})
}
複製代碼

在使用時能夠經過promise對象的內置方法then進行調用,then有兩個函數參數,分別表示promise對象中調用resolve和reject時執行的函數

function betray(){
	return new Promise(function(resolve,reject){
		setTimeout(function(){
			resolve();
		},1000)
	})
}


betray().then(function(){
	...//對應resolve時執行的邏輯
},function(){
	...//對應reject時執行的邏輯
})
複製代碼

也能夠用 catch 來執行失敗態

catch方法,用於註冊 onRejected 回調

在這裏要明白兩件事情:

  • catch實際上是then的簡寫,then(null,callback)
  • then方法調用以後,仍然返回的是promise對象,因此能夠鏈式調用

使用以下:

betary().then(res=>...//對應resolve時執行的邏輯).catch(err=>...//對應reject時執行的邏輯)
複製代碼

可使用多個then來實現鏈式調用,then的函數參數中會默認返回promise對象

betray().then(function(){
	...//對應resolve時執行的邏輯
},function(){
	...//對應reject時執行的邏輯
})
.then(function(){
	...//上一個then返回的promise對象對應resolve狀態時執行的邏輯
},function(){
	...//上一個then返回的promise對象對應reject狀態時執行的邏輯
})
複製代碼

使用promise來解決回調地獄的作法就是使用then的鏈式調用

function fnA(){
	return new Promise(resolve=>{
		...//異步操做中resolve
	})
}
function fnB(){
	return new Promise(resolve=>{
		...//異步操做中resolve
	})
}
function fnC(){
	return new Promise(resolve=>{
		...//異步操做中resolve
	})
}

fnA()
.then(()=>{
	return fnB()
})
.then(()=>{
	return fnC()
})
複製代碼

特色是:

  • then方法一般是表示異步操做成功時的回調,也能夠用catch方法表示異步操做失敗時的回調
  • 在調用的時候then在先後,catch在後
  • then方法能夠調用屢次,前一個then的返回值,會做爲後一個then的參數
  • 支持鏈式調用

async/await

async、await是什麼?

async顧名思義是「異步」的意思,async用於聲明一個函數是異步的。而await從字面意思上是「等待」的意思,就是用於等待異步完成。而且await只能在async函數中使用

一般async、await都是跟隨Promise一塊兒使用的。爲何這麼說呢?由於async返回的都是一個Promise對象同時async適用於任何類型的函數上。這樣await獲得的就是一個Promise對象(若是不是Promise對象的話那async返回的是什麼 就是什麼);

await獲得Promise對象以後就等待Promise接下來的resolve或者reject。

async、await解決了什麼?

傳統的回調地獄式寫法:

getData(a=>{
	getMoreData(a,b=>{
		getMoreData(b,c=>{
			console.log(c)
		});
	});
});
//不行了,再多寫要迷了
複製代碼

Promise改進後的寫法:

getData()
.then(a=>getMoreData(a))
.then(b=>getMoreData(b))
.then(c=>getMoreData(c))
複製代碼

async/await改進後:

(async()=>{
	const a = await getData;
	const b = await.getMoreData(a);
	const c = await.getMoreData(b);
	const d = await.getMoreData(c);
})();
複製代碼

async、await寫法

先來看看同步寫法:

console.log(1);

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

console.log(3);
複製代碼

輸出結果:

1
3
2
複製代碼

能夠看到輸出的順序並非咱們代碼中所寫的那樣,下面來看下async、await是如何解決這個問題的

(async function () {

  console.log(1);

  await new Promise(function (resolve, reject) { 
    setTimeout(function () {
      console.log(2);
      resolve();
    }, 1000);
  });

  console.log(3);

}())
複製代碼

輸出結果:

1
2
3
複製代碼

能夠看到這種寫法的輸出已經符合了咱們的預期

async 的定義:

  • async函數會返回一個Promise對象
  • 若是async函數中是return一個值,這個值就是Promise對象中resolve的值
  • 若是async函數中是throw一個值,這個值就是Promise對象中reject的值

await 的定義:

  • await只能在async裏面
  • await後面要跟一個promise對象

常規的promise對象會被js先暫存到eventloop(事件隊列)中,由於js是單線程執行的,等執行棧空了以後,纔會將事件隊列中的事件取出放入執行棧中執行

上述代碼中先是將整段代碼改形成了一個async(async能夠用於任何函數)函數,而後又將setTimeOut改形成了一個Promise對象


使用第三方Promise庫

下面簡單介紹一下第三方的Promise庫

對開發中使用promise進行小結:

  • 沒有異步,就不須要promise
  • 不使用promise,其實也是能夠解決異步編程的問題。使用promise,會使異步的編碼變得更加優雅,功能會更強
  • 在進行promise編程的使用,有以下兩個場景:
    • 直接使用別人封裝好的promise對象,好比fetch、axios
    • 須要本身封裝promise對象

注意:axios和fetch必須使用promise方式,如:

圖片加載失敗!

針對本身封裝promise對象,又能夠有以下兩種方式:

  • 本身封裝
  • 可使用第三方的promise庫

好比,針對第三方的promise庫,有兩個知名的庫:

  • bluebird
  • q.js

能夠利用bluebird 和 q.js 快速的生成promise對象。

以bluebird爲例,在服務端演示其用法。

第一步:安裝

圖片加載失敗!

第二步:使用

圖片加載失敗!

是否是以爲 so-easy

以上就是解決JS異步的三種方法,還有好多不足之處,但願能夠繼續學習和深刻理解


^_<

相關文章
相關標籤/搜索