2018-6-27 seo達人
如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里
Promise 是異步編程的一種解決方案,比傳統的解決方案–回調函數和事件--更合理和更強大。它由社區最早提出和實現,ES6將其寫進了語言標準,統一了語法,原生提供了Promise
所謂Promise ,簡單說就是一個容器,里面保存著某個未來才回結束的事件(通常是一個異步操作)的結果。從語法上說,Promise是一個對象,從它可以獲取異步操作的消息。
Promise 對象的狀態不受外界影響
三種狀態:
狀態改變:
Promise對象的狀態改變,只有兩種可能:
這兩種情況只要發生,狀態就凝固了,不會再變了,這時就稱為resolved(已定型
基本用法
ES6規定,Promise對象是一個構造函數,用來生成Promise實例
-
const promist = new Promise(function(resolve,reject){
-
if(/*異步操作成功*/){
-
resolve(value);
-
}else{
-
reject(error);
-
}
-
})
resolve
函數的作用是,將Promise對象的狀態從“未完成”變為“成功”(即從 pending
變為 resolved
),在異步操作成功時調用,并將異步操作的結果,作為參數傳遞出去;
reject
函數的作用是,將Promise對象的狀態從“未完成”變為“失敗”(即從 pending
變為 rejected
),在異步操作失敗時調用,并將異步操作報出的錯誤,作為參數傳遞出去。
Promise的源碼分析:
曾幾何時,我們的代碼是這樣的,為了拿到回調的結果,不得不callback hell
,這種環環相扣的代碼可以說是相當惡心了
-
let fs = require('fs')
-
fs.readFile('./a.txt','utf8',function(err,data){
-
fs.readFile(data,'utf8',function(err,data){
-
fs.readFile(data,'utf8',function(err,data){
-
console.log(data)
-
})
-
})
-
})
終于,我們的蓋世英雄
出現了,他身披金甲圣衣、駕著七彩祥云。好吧打岔兒了,沒錯他就是我們的Promise
,那讓我們來看看用了Promise
之后,上面的代碼會變成什么樣吧
-
let fs = require('fs')
-
function read(url){
-
return new Promise((resolve,reject)=>{
-
fs.readFile(url,'utf8',function(error,data){
-
error && reject(error)
-
resolve(data)
-
})
-
})
-
}
-
-
read('./a.txt').then(data=>{
-
return read(data)
-
}).then(data=>{
-
return read(data)
-
}).then(data=>{
-
console.log(data)
-
})
首先我們要知道自己手寫一個Promise
,應該怎么去寫,誰來告訴我們怎么寫,需要遵循什么樣的規則。當然這些你都不用擔心,其實業界都是通過一個規則指標來生產Promise
的。讓我們來看看是什么東西。傳送門?Promise/A+
我們先聲明一個類,叫做Promise
,里面是構造函數。如果es6還有問題的可以去阮大大的博客上學習一下(傳送門?es6)
-
class Promise{
-
constructor(executor){
-
//控制狀態,使用了一次之后,接下來的都不被使用
-
this.status = 'pendding'
-
this.value = undefined
-
this.reason = undefined
-
-
//定義resolve函數
-
let resolve = (data)=>{
-
//這里pendding,主要是為了防止executor中調用了兩次resovle或reject方法,而我們只調用一次
-
if(this.status==='pendding'){
-
this.status = 'resolve'
-
this.value = data
-
}
-
}
-
-
//定義reject函數
-
let reject = (data)=>{
-
if(this.status==='pendding'){
-
this.status = 'reject'
-
this.reason = data
-
}
-
}
-
-
//executor方法可能會拋出異常,需要捕獲
-
try{
-
//將resolve和reject函數給使用者
-
executor(resolve,reject)
-
}catch(e){
-
//如果在函數中拋出異常則將它注入reject中
-
reject(e)
-
}
-
}
-
}
那么接下來我會分析上面代碼的作用,原理
executor:
這是實例Promise
對象時在構造器中傳入的參數,一般是一個function(resolve,reject){}
status:``Promise
的狀態,一開始是默認的pendding狀態,每當調用道resolve和reject方法時,就會改變其值,在后面的then方法中會用到
value:
resolve回調成功后,調用resolve方法里面的參數值
reason:
reject回調成功后,調用reject方法里面的參數值
resolve:
聲明resolve方法在構造器內,通過傳入的executor方法傳入其中,用以給使用者回調
reject:
聲明reject方法在構造器內,通過傳入的executor方法傳入其中,用以給使用者回調
then方法是Promise
中最為重要的方法,他的用法大家都應該已經知道,就是將Promise
中的resolve或者reject的結果拿到,那么我們就能知道這里的then方法需要兩個參數,成功回調和失敗回調,上代碼!
-
then(onFufilled,onRejected){
-
if(this.status === 'resolve'){
-
onFufilled(this.value)
-
}
-
if(this.status === 'reject'){
-
onRejected(this.reason)
-
}
-
}
這里主要做了將構造器中resolve和reject的結果傳入onFufilled
和onRejected
中,注意這兩個是使用者傳入的參數,是個方法。所以你以為這么簡單就完了?要想更Swag
的應對各種場景,我們必須得再完善。繼續往下走!
之前我們只是處理了同步情況下的Promise,簡而言之所有操作都沒有異步的成分在內。那么如果是異步該怎么辦?
最早處理異步的方法就是callback,就相當于我讓你幫我掃地,我會在給你發起任務時給你一個手機,之后我做自己的事情去,不用等你,等你掃完地就會打手機給我,誒,我就知道了地掃完了。這個手機就是callback,回調函數。
首先我們需要改一下構造器里的代碼,分別添加兩個回調函數的數組,分別對應成功回調和失敗回調。他們的作用是當成功執行resolve或reject時,執行callback。
-
//存放成功回調的函數
-
this.onResolvedCallbacks = []
-
//存放失敗回調的函數
-
this.onRejectedCallbacks = []
-
-
let resolve = (data)=>{
-
if(this.status==='pendding'){
-
this.status = 'resolve'
-
this.value = data
-
//監聽回調函數
-
this.onResolvedCallbacks.forEach(fn=>fn())
-
}
-
}
-
let reject = (data)=>{
-
if(this.status==='pendding'){
-
this.status = 'reject'
-
this.reason = data
-
this.onRejectedCallbacks.forEach(fn=>fn())
-
}
-
}
然后是then需要多加一個狀態判斷,當Promise中是異步操作時,需要在我們之前定義的回調函數數組中添加一個回調函數。
-
if(this.status === 'pendding'){
-
this.onResolvedCallbacks.push(()=>{
-
// to do....
-
let x = onFufilled(this.value)
-
resolvePromise(promise2,x,resolve,reject)
-
})
-
this.onRejectedCallbacks.push(()=>{
-
let x = onRejected(this.reason)
-
resolvePromise(promise2,x,resolve,reject)
-
})
-
}
ok!大功告成,異步已經解決了
這也是
Promise
中的重頭戲,我來介紹一下,我們在用Promise的時候可能會發現,當then函數中return了一個值,我們可以繼續then下去,不過是什么值,都能在下一個then中獲取,還有,當我們不在then中放入參數,例:promise.then().then()
,那么其后面的then依舊可以得到之前then返回的值,可能你現在想很迷惑。讓我來解開你心中的憂愁,follow me。
-
then(onFufilled,onRejected){
-
//解決onFufilled,onRejected沒有傳值的問題
-
onFufilled = typeof onFufilled === 'function'?onFufilled:y=>y
-
//因為錯誤的值要讓后面訪問到,所以這里也要跑出個錯誤,不然會在之后then的resolve中捕獲
-
onRejected = typeof onRejected === 'function'?onRejected:err=>{ throw err ;}
-
//聲明一個promise對象
-
let promise2
-
if(this.status === 'resolve'){
-
//因為在.then之后又是一個promise對象,所以這里肯定要返回一個promise對象
-
promise2 = new Promise((resolve,reject)=>{
-
setTimeout(()=>{
-
//因為穿透值的緣故,在默認的跑出一個error后,不能再用下一個的reject來接受,只能通過try,catch
-
try{
-
//因為有的時候需要判斷then中的方法是否返回一個promise對象,所以需要判斷
-
//如果返回值為promise對象,則需要取出結果當作promise2的resolve結果
-
//如果不是,直接作為promise2的resolve結果
-
let x = onFufilled(this.value)
-
//抽離出一個公共方法來判斷他們是否為promise對象
-
resolvePromise(promise2,x,resolve,reject)
-
}catch(e){
-
reject(e)
-
}
-
},0)
-
})
-
}
-
if(this.status === 'reject'){
-
promise2 = new Promise((resolve,reject)=>{
-
setTimeout(()=>{
-
try{
-
let x = onRejected(this.reason)
-
resolvePromise(promise2,x,resolve,reject)
-
}catch(e){
-
reject(e)
-
}
-
},0)
-
})
-
}
-
if(this.status === 'pendding'){
-
promise2 = new Promise((resolve,reject)=>{
-
this.onResolvedCallbacks.push(()=>{
-
// to do....
-
setTimeout(()=>{
-
try{
-
let x = onFufilled(this.value)
-
resolvePromise(promise2,x,resolve,reject)
-
}catch(e){
-
reject(e)
-
}
-
},0)
-
})
-
this.onRejectedCallbacks.push(()=>{
-
setTimeout(()=>{
-
try{
-
let x = onRejected(this.reason)
-
resolvePromise(promise2,x,resolve,reject)
-
}catch(e){
-
reject(e)
-
}
-
})
-
})
-
})
-
}
-
return promise2
-
}
一下子多了很多方法,不用怕,我會一一解釋
Promise
?:首先我們要注意的一點是,then有返回值,then了之后還能在then,那就說明之前的then返回的必然是個Promise
。
setTimeout
?:因為Promise本身是一個異步方法,屬于微任務一列,必須得在執行棧執行完了在去取他的值,所以所有的返回值都得包一層異步setTimeout。
resolvePromise
是什么?:這其實是官方Promise/A+的需求。因為你的then可以返回任何職,當然包括Promise
對象,而如果是Promise
對象,我們就需要將他拆解,直到它不是一個Promise
對象,取其中的值。
那就讓我們來看看這個
resolvePromise
到底長啥樣。
-
function resolvePromise(promise2,x,resolve,reject){
-
//判斷x和promise2之間的關系
-
//因為promise2是上一個promise.then后的返回結果,所以如果相同,會導致下面的.then會是同一個promise2,一直都是,沒有盡頭
-
if(x === promise2){//相當于promise.then之后return了自己,因為then會等待return后的promise,導致自己等待自己,一直處于等待
-
return reject(new TypeError('循環引用'))
-
}
-
//如果x不是null,是對象或者方法
-
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
-
//為了判斷resolve過的就不用再reject了,(比如有reject和resolve的時候)
-
let called
-
try{//防止then出現異常,Object.defineProperty
-
let then = x.then//取x的then方法可能會取到{then:{}},并沒有執行
-
if(typeof then === 'function'){
-
//我們就認為他是promise,call他,因為then方法中的this來自自己的promise對象
-
then.call(x,y=>{//第一個參數是將x這個promise方法作為this指向,后兩個參數分別為成功失敗回調
-
if(called) return;
-
called = true
-
//因為可能promise中還有promise,所以需要遞歸
-
resolvePromise(promise2,y,resolve,reject)
-
},err=>{
-
if(called) return;
-
called = true
-
//一次錯誤就直接返回
-
reject(err)
-
})
-
}else{
-
//如果是個普通對象就直接返回resolve作為結果
-
resolve(x)
-
}
-
}catch(e){
-
if(called) return;
-
called = true
-
reject(e)
-
}
-
}else{
-
//這里返回的是非函數,非對象的值,就直接放在promise2的resolve中作為結果
-
resolve(x)
-
}
-
}
它的作用是用來將onFufilled的返回值進行判斷取值處理,把最后獲得的值放入最外面那層的
Promise
的resolve函數中。
promise2
(then函數返回的Promise對象),x
(onFufilled函數的返回值),resolve、reject
(最外層的Promise上的resolve和reject)。
promise2
和x
?:首先在Promise/A+中寫了需要判斷這兩者如果相等,需要拋出異常,我就來解釋一下為什么,如果這兩者相等,我們可以看下下面的例子,第一次p2是p1.then出來的結果是個Promise
對象,這個Promise
對象在被創建的時候調用了resolvePromise(promise2,x,resolve,reject)函數,又因為x等于其本身,是個Promise
,就需要then方法遞歸它,直到他不是Promise
對象,但是x(p2)的結果還在等待,他卻想執行自己的then方法,就會導致等待。
-
let p1 = new Promise((resolve,reject)=>{
-
resolve()
-
})
-
-
let p2 = p1.then(d=>{
-
return p2
-
})
resolvePromise
函數已經resolve或者reject了,那就不需要在執行下面的resolce或者reject。
resolvePromise
函數?:相信細心的人已經發現了,我這里使用了遞歸調用法,首先這是Promise/A+中要求的,其次是業務場景的需求,當我們碰到那種Promise的resolve里的Promise的resolve里又包了一個Promise的話,就需要遞歸取值,直到x不是Promise對象。
我們現在已經基本完成了Promise的then方法,那么現在我們需要看看他的其他方法。
相信大家都知道catch這個方法是用來捕獲Promise中的reject的值,也就是相當于then方法中的onRejected回調函數,那么問題就解決了。我們來看代碼。
-
//catch方法
-
catch(onRejected){
-
return this.then(null,onRejected)
-
}
該方法是掛在Promise原型上的方法。當我們調用catch傳callback的時候,就相當于是調用了then方法。
大家一定都看到過Promise.resolve()、Promise.reject()
這兩種用法,它們的作用其實就是返回一個Promise對象,我們來實現一下。
-
//resolve方法
-
Promise.resolve = function(val){
-
return new Promise((resolve,reject)=>{
-
resolve(val)
-
})
-
}
-
//reject方法
-
Promise.reject = function(val){
-
return new Promise((resolve,reject)=>{
-
reject(val)
-
})
-
}
這兩個方法是直接可以通過class調用的,原理就是返回一個內部是resolve或reject的Promise對象。
all方法可以說是Promise中很常用的方法了,它的作用就是將一個數組的Promise對象放在其中,當全部resolve的時候就會執行then方法,當有一個reject的時候就會執行catch,并且他們的結果也是按著數組中的順序來排放的,那么我們來實現一下。
-
//all方法(獲取所有的promise,都執行then,把結果放到數組,一起返回)
-
Promise.all = function(promises){
-
let arr = []
-
let i = 0
-
function processData(index,data){
-
arr[index] = data
-
i++
-
if(i == promises.length){
-
resolve(arr)
-
}
-
}
-
return new Promise((resolve,reject)=>{
-
for(let i=0;i<promises.length;i++){
-
promises[i].then(data=>{
-
processData(i,data)
-
},reject)
-
}
-
})
-
}
其原理就是將參數中的數組取出遍歷,每當執行成功都會執行
processData
方法,processData
方法就是用來記錄每個Promise的值和它對應的下標,當執行的次數等于數組長度時就會執行resolve,把arr的值給then。這里會有一個坑,如果你是通過arr數組的長度來判斷他是否應該resolve的話就會出錯,為什么呢?因為js數組的特性,導致如果先出來的是1位置上的值進arr,那么0位置上也會多一個空的值,所以不合理。
race方法雖然不常用,但是在Promise方法中也是一個能用得上的方法,它的作用是將一個Promise
數組放入race中,哪個先執行完,race就直接執行完,并從then中取值。我們來實現一下吧。
-
//race方法
-
Promise.race = function(promises){
-
return new Promise((resolve,reject)=>{
-
for(let i=0;i<promises.length;i++){
-
promises[i].then(resolve,reject)
-
}
-
})
-
}
原理大家應該看懂了,很簡單,就是遍歷數組執行Promise,如果有一個
Promise
執行成功就resolve。
語法糖這三個字大家一定很熟悉,作為一個很Swag的前端工程師,對async/await這對兄弟肯定很熟悉,沒錯他們就是generator的語法糖。而我們這里要講的語法糖是Promise的。
-
//promise語法糖 也用來測試
-
Promise.deferred = Promise.defer = function(){
-
let dfd = {}
-
dfd.promise = new Promise((resolve,reject)=>{
-
dfd.resolve = resolve
-
dfd.reject = reject
-
})
-
return dfd
-
}
什么作用呢?看下面代碼你就知道了
-
let fs = require('fs')
-
let Promise = require('./promises')
-
//Promise上的語法糖,為了防止嵌套,方便調用
-
//壞處 錯誤處理不方便
-
function read(){
-
let defer = Promise.defer()
-
fs.readFile('./1.txt','utf8',(err,data)=>{
-
if(err)defer.reject(err)
-
defer.resolve(data)
-
})
-
return defer.Promise
-
}
沒錯,我們可以方便的去調用他語法糖defer中的
Promise
對象。那么它還有沒有另外的方法呢?答案是有的。我們需要在全局上安裝promises-aplus-tests插件npm i promises-aplus-tests -g
,再輸入promises-aplus-tests [js文件名] 即可驗證你的Promise的規范。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
藍藍設計的小編 http://www.syprn.cn