淺談promise和js執行機制(一)

做爲一個入門級前端,今天是一個很是值得記念的日子,由於這是我第一次在論壇上發表帖子,做爲起步。雖然我以爲本身水平仍是十分的有限,對一些細節的理解還不是很透徹,可是仍是要邁出這一步,不論是給別的新手做爲學習參考,仍是本身之後回顧,總以爲須要把本身的成長記錄下來,但願本身之後仍是要多堅持,若是有不對的地方仍是但願你們及時提出來,共同進步前端

今天有時間翻到了es6的promise,可能你們都對此熟悉不過,我以前一直以爲promise也很簡單,可是今天確實讓我對promise有了一個新的瞭解,之前的理解多是錯誤的。。。先來看看官方的promise的定義是:es6

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

特色:promise

(1)對象的狀態不受外界影響。Promise對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是「承諾」,表示其餘手段沒法改變bash

(2)一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved(已定型)。若是改變已經發生了,你再對Promise對象添加回調函數,也會當即獲得這個結果。這與事件(Event)徹底不一樣,事件的特色是,若是你錯過了它,再去監聽,是得不到結果的。異步

初讀上面這段話我讀了不下5次,可是我仍是沒能真正瞭解其真正表達的意思,因而我查閱了部分資料,終於找到了一個比較容易理解的說法,這個估計小白應該是能夠看得懂得。編輯器

就拿作飯吃飯洗碗來舉例子吧,這是三個步驟,第一步作飯,再第二步吃飯的時候咱們須要拿到第一步作的飯,在第三步洗碗的時候咱們須要拿到第二步的碗筷,並且這三個步驟必須是按照順序執行,有嚴格的前後順序。函數

//作飯
function cook(){
    console.log('開始作飯。');
    var p = new Promise(function(resolve, reject){        //作一些異步操做
        setTimeout(function(){
            console.log('作飯完畢!');
            resolve('雞蛋炒飯');
        }, 1000);
    });
    return p;
}
 
//吃飯
function eat(data){
    console.log('開始吃飯:' + data);
    var p = new Promise(function(resolve, reject){        //作一些異步操做
        setTimeout(function(){
            console.log('吃飯完畢!');
            resolve('一個碗和一雙筷子');
        }, 2000);
    });
    return p;
}
 //洗碗
function wash(data){
    console.log('開始洗碗:' + data);
    var p = new Promise(function(resolve, reject){        //作一些異步操做
        setTimeout(function(){
            console.log('洗碗完畢!');
            resolve('乾淨的碗筷');
        }, 2000);
    });
    return p;
}

//函數調用
cook().then(res1 => {
	console.log(res1,'這是第一步傳遞給第二步的參數')
	return eat(res1)
}).then(res2 => {
	console.log(res2,'這是第二步傳給第三步的參數')
	return wash(res2)
}).then(res3 => {
	console.log(res3,'飯吃完了還你乾淨的碗筷')
})

複製代碼

結果以下: 學習

看完上面的代碼你們可能會有好多疑問,我在這裏把我當時初學promise的疑問和你們分享一下:ui

(1)爲何我要在promise對象外面須要用一個函數來包裹起來呢?

答:Promise也有一些缺點。首先,沒法取消Promise,一旦新建它就會當即執行,沒法中途取消。其次,若是不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。第三,當處於pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。

這是promise的一些缺點,一旦新建它就會當即執行 因此爲了控制這個promise對象何時執行,在開發過程當中咱們須要在外面包裹一個函數,經過調用函數的形式來控制promise的執行。你們能夠在本身的編輯器中試試。

new Promise(function(resolve, reject) {
	setTimeout(()=> {
		console.log('開車!!!')
	},2000)
});
複製代碼

(2)在每個函數中我爲何要將個人promise對象用一個變量接收而後return出去呢?

答:由於有了Promise對象,就能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操做更加容易。

//函數調用
cook().then(res1 => {
	console.log(res1,'這是第一步傳遞給第二步的參數')
	return eat(res1)
}).then(res2 => {
	console.log(res2,'這是第二步傳給第三步的參數')
	return wash(res2)
}).then(res3 => {
	console.log(res3,'飯吃完了還你乾淨的碗筷')
})
複製代碼

你們看個人這段代碼 在執行了cook()函數的時候cook函數return出來一個promise對象,promise對象上有.then()或.catch()方法,那麼直接cook().then就能夠在.then()方法中咱們能夠拿到promise對象中向外傳遞的參數,這個參數咱們將傳遞給下一個eat()函數,做爲eat()函數的參數繼續執行。 而eat()函數也return了一個promise對象。那麼咱們將咱們的這段代碼拆解一下:

第一步: cook()    //拿到的是cook return出來的promise對象

第二步: cook().then(res => {
 console.log(res)    //拿到內部向外部傳遞的參數 
})

第三步: cook().then(res => {
    console.log(res)
    return eat(res)    //eat執行之後return的是eat的promise對象,而後再把這個對象繼續向外return
})    //那麼第三步的最終結果就是eat()的promise對象

第四步: cook().then(res => {
    console.log(res)
    return eat(res) //此時的結果是eat()的promise對象
}).then(res2 => {
    // 此時eat的promise又有.then方法 .....以此類推
})

複製代碼

咱們就這樣一步一步的完成了整個作飯、吃飯、洗碗的整個流程。 縱觀以上代碼你會發現雖然每個操做流程中我都是以異步函數setTimeout來進行的,可是在調用過程當中確是按照 作飯-吃飯-洗碗的正常流程表達的。這不是promise的有了Promise對象,就能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數

(3)如何判斷失敗的狀況呢?

//作飯
function cook(){
    console.log('開始作飯。');
    var p = new Promise(function(resolve, reject){        //作一些異步操做
        setTimeout(function(){
            console.log('飯糊了....無法吃');
            reject('糊了的飯');
        }, 1000);
    });
    return p;
}
//吃飯
function eat(data){
    console.log('開始吃飯:' + data);
    var p = new Promise(function(resolve, reject){        //作一些異步操做
        setTimeout(function(){
            console.log('吃飯完畢!');
            resolve('一個碗和一雙筷子');
        }, 2000);
    });
    return p;
}

cook().then(res1 => {
	console.log(res1,'這是第一步傳遞給第二步的參數')
	return eat(res1)
}).catch(err => {
	console.log(err,'返回錯誤')
})
複製代碼

catch()方法用來指定 reject 的回調。

(4)如何在多個異步操做都完成後才執行回調呢?

function tackBus1(){
    var p = new Promise(function(resolve, reject){
        //作一些異步操做
        setTimeout(function(){
            console.log('甲正在上車');
            resolve('甲坐好了');
        }, 1000);
    });
    return p;            
}
function tackBus2(){
    var p = new Promise(function(resolve, reject){
        //作一些異步操做
        setTimeout(function(){
            console.log('乙正在上車');
            resolve('乙坐好了');
        }, 2000);
    });
    return p;            
}
function tackBus3(){
    var p = new Promise(function(resolve, reject){
        //作一些異步操做
        setTimeout(function(){
            console.log('丙正在上車');
            resolve('丙坐好了');
        }, 3000);
    });
    return p;            
}
Promise.all([tackBus1(),tackBus2(),tackBus3()]).then(res => {
	console.log(res)
})
複製代碼

司機在等人上車,在乘客都坐穩了之後開車發車,這就用Promise.all來執行,all接收一個數組參數,裏面的值最終都算返回Promise對象。這樣,三個異步操做的並行執行的,等到它們都執行完後纔會進到then裏面。那麼,三個異步操做返回的數據哪裏去了呢?都在then裏面呢,all會把全部異步操做的結果放進一個數組中傳給then,就是上面的results。因此上面代碼的輸出結果就是:

這是.all的方法,promise還有一種.race的方法,它和all方法的區別就是: all方法的效果其實是誰跑的慢,以誰爲準執行回調,那麼相對的就有另外一個方法誰跑的快,以誰爲準執行回調,剛剛的執行結果我設置了他們的時間間隔分別是1s,2s,3s,在等最慢的執行完之後才執行了all這個回調方法,如今我們來試試promise.race方法

function tackBus1(){
    var p = new Promise(function(resolve, reject){
        //作一些異步操做
        setTimeout(function(){
            console.log('甲正在上車');
            resolve('甲坐好了');
        }, 1000);
    });
    return p;            
}
function tackBus2(){
    var p = new Promise(function(resolve, reject){
        //作一些異步操做
        setTimeout(function(){
            console.log('乙正在上車');
            resolve('乙坐好了');
        }, 2000);
    });
    return p;            
}
function tackBus3(){
    var p = new Promise(function(resolve, reject){
        //作一些異步操做
        setTimeout(function(){
            console.log('丙正在上車');
            resolve('丙坐好了');
        }, 3000);
    });
    return p;            
}
Promise.race([tackBus1(),tackBus2(),tackBus3()]).then(res => {
	console.log(res)
})
複製代碼

結果以下:

能夠看出來在甲執行完畢後當即就執行了.race()方法,可是不耽誤其餘兩個異步操做的進行,.race()中拿到的參數也只是當前最早執行完的異步操做中傳遞出來的參數。

(4)由promise聯繫到的js的宏任務和微任務

在看到promise的時候有一個地方仍是令我有困惑,如今先留一個懸念,你們能夠先看看下面這段代碼,你以爲輸出結果是什麼呢? 咱們下回見!

setTimeout(function(){
  console.log('1')
});
new Promise(function(resolve){
    console.log('2');
    resolve();
}).then(function(){
    console.log('3')
});
console.log('4');
複製代碼
相關文章
相關標籤/搜索