Promise對象學習筆記

Promise對象簡介

Promise是異步編程的一種解決方案,比傳統的解決方案-回調函數和事件-更合理更強大。簡單講,Promise裏儲存着某個將來纔會結束的事件的結果,從它能夠獲取異步操做的消息。 Promise對象有如下兩個特色:javascript

  • 對象的狀態不受外界影響。Promise有三種狀態:Pending(進行中),Fulfilled(已成功),Rejected(已失敗)。只有異步操做的結果才能決定當前是哪一種狀態。
  • 一旦狀態改變就不會再變化,而且Promise的變化只會有兩種狀況:Pending變成Fulfill或Pending變成Rejected。只要這兩種狀況發生,則一直保持這個結果,此時成爲Resolved(已定型。下文中爲保持統一,Resolved代指Fulfilled狀態,不包含Rejected狀態)。

Promise能夠方便的將異步操做以同步操做的流程表達出來,避免層層嵌套的回調函數。java

基本用法

先建立一個Promise實例:編程

const promise = new Promise((resolve, reject) {
    // ...some code
    if(/*異步操做完成*/) {
        resolve(value)
    else {
        reject(error)
    }
})
複製代碼

Promise構造函數接收一個函數做爲參數,該函數的兩個參數分別是resolve函數和reject函數。resolve函數的做用是將Promise的狀態從pending變成resolved,在異步操做成功時調用,並將結果做爲參數傳遞出去;reject函數的做用時將Promsie的狀態從pending變成rejected,在異步操做失敗時調用,並將異步操做的錯誤做爲參數傳遞出去。json

Promise的then方法能夠分別指定Resolved狀態和rejected狀態的回調函數。其中第一個回調做爲Resolved狀態的回調,第二個回調做爲rejected狀態的回調,而第二個回調是可選的。數組

執行順序

下面咱們來猜一猜下面的代碼的打印順序是什麼樣的:promise

let promise = new Promise((resolve, reject)=>{
    console.log('Promise');
    resolve()
})
promise.then(() => {
    console.log('Resolved');
})
console.log('Hi')
複製代碼

讓咱們先來推理一下:首先,Promise構造函數中的代碼會在新建的時候當即執行,因此,第一個打印的應該是「Promise」;其次then方法指定的回調函數會在當前腳本全部同步任務執行完以後才執行,因此「Resolved」應該是最後打印的。總上所述。打印順序應該是bash

// Promise
// Hi
// Resolved
複製代碼

還有一點須要注意:resolve和reject並不會阻止後面代碼的執行,而且後面的代碼還會先執行markdown

new Promise((resolve, reject) => {
    resolve(1);
    console.log(2);
}).then((res) => {
    console.log(res)
})
// 2
// 1
複製代碼

這是由於執行resolved是在本次事件循環的末尾執行,老是晚於本次循環的同步任務。 通常來講,不該該在resolve或reject以後寫代碼,應該將其放在then方法裏面。爲了預防萬一,咱們能夠在resolve和reject前加上return語句。異步

給resolve傳遞Promise實例做爲參數的狀況

請看下面的例子,來猜一猜最後會進入then仍是catch呢:異步編程

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(()=> resolve(p1), 1000)
})
p2.then((res)=> {console.log(res)}).catch((err) => {console.log(err)});

// Error:fail
複製代碼

答案是會進入catch。這是由於p1的狀態會傳遞給p2,也就是說p1的狀態決定了p2的狀態,若是p1是pending那麼p2的回調會等待p1的狀態改變;若是p1的狀態是resolved或rejected,那麼p2的回調會當即執行。因此上面的代碼會在3秒後觸發catch指定的回調。

Promise.prototype.then()

then方法的做用是爲Promise實例添加狀態改變時的回調函數。它的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。
注意:then方法返回的是一個新的Promise實例(注意不是原來的Promise實例)。所以能夠採用鏈式寫法,即.then方法後面再調另外一個.then。而且上一個then方法返回的結果會做爲回調方法的參數。

採用鏈式的then方法能夠依次指定一組按照順序調用的回調函數,而且前一個回調函數可能返回的仍是一個Promise實例,然後面的回調函數則會等待該Promise對象狀態發生改變時再被調用。看下面代碼你就懂了:

getJSON('/posts.json')
.then(post => getJSON(post.commentURL))
.then(
comments => {console.log('resolved', comments)},
err => {console.log('rejected', err)}
)
複製代碼

上面的代碼getJSON返回的時一個Promise對象。因此第一個then返回的就是一個Promise,此時第二個then方法指定的回調函數就會等待這個新的Promise對象狀態發生改變時,resolved就調用第一個回調,rejected就會調用第二個回調。

Promise.prototype.catch()

catch方法是用於指定發生錯誤時的回調函數。注意它不止能夠捕獲Promise拋出的錯誤還能捕獲前一個回調函數運行時發生的錯誤。

getJSON('/posts.json').then(res => {
    throw new Error('error')
}).catch(err => {
    console.log(err)
})
// Error error
複製代碼

上面的代碼中,getJSON這個Promise對象的狀態若爲resolved,會進入then方法,若爲rejected則會進入catch方法。另外,若是then方法指定的回調在運行中拋出錯誤也會被catch捕捉。

Promise對象的錯誤具備'冒泡'的性質,會一直向後傳遞,直到被捕獲,也就是說錯誤老是會被下一個catch語句捕獲。

getJSON('/posts.json').then(post => {
    return getJSON(post.commentURL)
}) .then(comments => {
    // somecode
}).catch(err => {
    // 處理前面三個Promise產生的錯誤
})

複製代碼

咱們通常使用catch捕獲錯誤,而不使用then方法的第二個參數。

須要注意的是,catch方法返回的仍是一個Promise對象,所以後面還能接着調用then方法。

若沒有指定catch方法處理錯誤,Promise對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。

Promise.all()

Promise.all方法用於將多個Promise實例包裝成一個新的Promise實例。Promise.all方法接收的參數必須具備Iterator接口且返回的每一個成員都必須是Promise實例。若不是Promise實例,Promise.all方法內部就會先執行Promise.resolve方法將參數轉爲Promise實例。

let p = Promise.all([p1,p2,p3])
複製代碼

上面代碼中,p的狀態由p1,p2,p3決定,分爲兩種狀況:
一、只有p一、p二、p3的狀態都是Fulfilled,p的狀態纔會是Fulfilled,此時,p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。
二、只要p一、p二、p3任一個狀態爲rejected,則p的狀態爲rejected,返回第一個被rejected的實例的返回值給p的回調函數。

看下面的例子:

const promises = [1,2,3].map(id => {
    return getJSON('/post' + id + '.json')
});
Promise.all(promises).then((res) => {
    // ...
}).catch(err => {
    // ...
})
複製代碼

若是做爲參數的Promise實例自身定義了catch方法,那麼它被rejected時並不會觸發Promise.all的catch方法。 看下面例子:

const p1 = new Promise((resolve,reject) => {
    resolve('hello')
}).then((res) => res)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
    throw new Error('error!')
}).then(res => res)
.catch(err => err);

Promise.all([p1,p2])
.then(res => console.log(res))
.catch(err => {console.log(err)})
// ["hello", Error: error!]
複製代碼

上面的代碼中p2會rejected,可是Promise.all()會進入then的回調,這是由於p2有本身的catch方法,而該方法返回的是一個新的Promise實例,p2實際上指向的是這個實例,而這個實例執行完以後也會變成resolved,因此all方法中的兩個實例都是resolved,所以不會調用catch方法。

Promise.race()

race方法一樣是將多個Promise實例包裝成一個新的實例。

let p = Promise.race([p1,p2,p3])
複製代碼

不一樣之處是race方法的狀態在第一個參數實例率先改變狀態後,p的狀態就會跟着改變,並將那個率先改變的實例的返回值傳遞給p的回調。同all方法同樣,若參數中有不是Promise實例的,會先調用resolve方法,將參數轉換成Promise實例再進一步處理。

用下面的例子介紹下race的使用場景:

const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise((resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
})
])
p.then(res => console.log(res));
p.catch(err => console.log(err));
複製代碼

上面的代碼中,若是5秒內fetch方法無返回結果,則p的catch回調就會被觸發。

Promise.resolve()

有時候須要將現有的對象轉換成Promise.resolve對象,這時就要用到Promise.resolve方法。

Promise.resolve('foo');
// 等價於
new Promise(resolve => resolve('foo'))
複製代碼

Promise.resolve方法的參數分紅如下四種狀況: 一、Promise實例
若是參數時Promise實例,那麼Promise.resolve將不做任何修改,直接返回這個實例。

二、thanable對象
thanable對象是指具備then方法的對象,好比下面這個:

let thenable = {
    then: (resolve, reject) => {
        resolve(42)
    }
}
複製代碼

Promise.resolve方法會將這個對象轉換成Promise對象,並當即執行thenable對象的then方法。

三、不具備then方法的對象或者根本不是對象
若是參數是一個原始值或者不具備then方法的對象,那麼Promise.resolve返回的是一個新的Promise對象,狀態爲rejected,Promise.resolve方法的參數會同時傳給回調函數,而且回調會當即執行。

四、不帶任何參數 不帶任何參數的話,resolve會直接返回一個帶有resolved狀態的Promise對象,須要注意的是,當即resolved的Promise對象是在本輪事件循環結束時執行,而不是在下一輪事件循環開始時執行。

Promise.reject()

Promise.reject方法也會返回一個新的Promise實例,狀態爲Rejected。與Promise.resolve方法不一樣的一點是,reject方法的參數會原封不動的做爲reject的理由變成後續方法的參數。以下:

const thenable = {
    then: (resolve, reject) => {
        reject('error')
    }
}
Promise.reject(thenable).catch(e => {
    console.log(e === thenable)
})
// true
複製代碼

上面代碼中,Promise.reject方法的參數是一個thenable對象,然後面catch方法的參數不像resolve方法同樣,拋出的‘error’字符串,而是這個thenable對象自己。

總結

這篇文章是對阮一峯老師的《ES6標準入門》的一個學習筆記吧,內容大可能是來源於書中,在此整理一下方便後續複習鞏固。對Promise有其餘想法的同窗歡迎評論指教哦。

參考

阮一峯 《ES6標準入門》

相關文章
相關標籤/搜索