1. 程式人生 > >JS-原生/對於promise物件以及非同步機制的理解

JS-原生/對於promise物件以及非同步機制的理解

promise是ES6新增的語法,他的出現極大地改善了非同步程式設計中“回撥地獄”的情況,promise究竟有什麼用呢?在踩了很多坑之後,我總結出了一套自己對於promise的理解,本文爭取用一段最簡單的程式碼,讓新手秒懂promise的用法,老鳥輕拍!

我現在宣告一個a,並且要延時1秒鐘後給他賦值為10,然後列印這個a:

		var a;

		setTimeout(function (){
			a = 10;
		},1000);//1000ms後給a賦值為10

		console.log(a)//undefined,

很顯然,a是undefined,因為js是非阻塞的,它不會等你定時器裡的東西走完了,再執行列印a這個操作。

它不等你讀完,就會迫不及待地去列印a,因為a要1秒後才能被賦值,所以此時a就像一個早產兒一樣,只是個undefined。

那怎麼辦?很簡單,把【列印a】這個操作也放進延時定時器裡嘛:

		setTimeout(function (){
			a = 10;
			console.log(a);//成功列印為10
		},1000);

我想上一個例子並不難理解,那麼接下來我會試圖將其功能進行拓展,【列印a】這個需求我並不需要,我想要用a做其他事情,比如讓他乘以2並輸出返回結果,或者其他的涉及到a的操作。那很簡單,我將console.log(a)這段程式碼刪去,封裝成一個函式,並將其作為引數傳進這個定時器裡,也就是所謂的“回撥函式”如下:

		var a;

		function foo(callBack){
			setTimeout(function (){
				a = 10;
				callBack&&callBack()
			},1000)
		}

		foo(function (){ //因為a是全域性的,所以不需要將a傳參進foo的回撥中
			return a*2
		})

呼叫foo的時候,作為引數的這個匿名函式,就是所謂的回撥函式,回撥函式這個機制極大地增加了程式碼的可能性,你可以在回撥中做任何事,並且在不同的地方進行呼叫,你也將得到不同的反饋,如果將一個函式執行過程看成一個時間軸,那回調函式就可以允許你在這個過程中的各個時刻內執行其他程式碼,例如你可以將Vue.js的生命週期鉤子函式看成是不同時期的回撥函式

回撥函式的作用顯而易見,但如果一層回撥套一層回撥的話,程式碼極其不方便閱讀與修改,因此ES6提供了Promise建構函式,從形式上改善了回撥地獄。

上述列印a的例子可以改為:

		var a;

		var p = new Promise(function (resolve,reject){
			setTimeout(function (){
				a = 10;
				resolve();
				/*reject也就是失敗時對應的函式,由於這個例子比較簡單,就不再贅述,畢竟是面向新手的*/
			},1000)
		}).then(function (){
			console.log(a)
		})

看似很複雜,實際上只要記住一點,resolve對應的是then的第一個回撥函式,reject對應的是then的第二個回撥函式,原本回調版的程式碼順序咋寫的,promise還是咋寫,只不過把非同步執行(即需要等待資料的部分,比如我那個列印a,或者取一張img的width)的部分寫進then的回撥裡,然後把resolve當作這個位於then裡的回撥,在老地方進行呼叫就行了(reject同理)

現在我們就以一個人起床後的行為為例來深入理解非同步執行機制,來看程式碼:

getup();
wearClothes();
brushTeeth();
washFace();
eatBreakfast();
goToCompany();

因為js是非同步執行的,所以如果這麼寫,邏輯上等同於一個人同時開始做“起床+穿衣+刷牙+洗臉+吃早飯+出門上班”這些事,顯然是不可能的事情,與我們想要的結果大相徑庭,依照回撥函式的寫法,我們會在上述每個行為結束之後(即函式執行完畢的我們想要讓他做什麼,例如$.ajax()的success)傳入一個回撥函式,並將下一步的函式寫進回撥裡,環環相扣,就像這樣:

getUp(function (){
	wearClothes(function (){
		brushTeeth(function (){
			washFace(function (){
				eatBreakfast(function (){
					goToCompany()
				})
			})
		})
	})
})
這樣的形式相當噁心,相當難以維護,好在我們有promise:
var p = new Promise(function (resolve){
	getUp(function (){
		resolve();
	});
}).then(function (){
	return new Promise(function (resolve){
		wearClothes(function (){
			resolve();
		});
	})
}).then(function (){
	return new Promise(function (resolve){
		brushTeeth(function (){
			resolve();
		});
	})
}).then(function (){
	return new Promise(function (resolve){
		washFace(function (){
			resolve();
		});
	})
}).then(function (){
	return new Promise(function (resolve){
		eatBreakfast(function (){
			resolve();
		});
	})
}).then(function (){
	goToCompany()
})

如果要鏈式呼叫then的話,必須要注意的一點是,一定要在then裡的回撥函式return一個新的promise物件,否則執行順序將向上開始查詢,直到找到最近的promise例項。

寫到這裡大家會發現promise並沒有改變什麼,只是把非同步程式碼的書寫形式從水平方向改為了豎直方向,讓程式碼更符合人類的閱讀習慣

關於promise的初步入門就先到這,若有錯誤歡迎指出。