重學Es6 Promise

Promise 含義

Promise 是異步編程的一種解決方案,比傳統的回調更加合理而其人強大。javascript

Promise,簡單來講,是一個容器,裏面存放着某個將來纔會結束的事件的結果。Promise是一個對象,用來獲取異步操做的消息。java

Promise有如下兩個特色:編程

1 對象狀態不受外界影響。json

有三種狀態:Pending(進行中),Fulfilled(已成功),Rejected(已失敗)。只有異步操做的結果能夠決定當前是哪種狀態,任何操做都沒法改變這個狀態。數組

2 一旦改變,不會再變,任什麼時候候均可以獲得這個結果。promise

Promise對象的狀態改變只有兩種可能:從Pending 到 Fulfilled 或者 從Pending 到 Rejected。只要這兩種狀況發生,狀態就會凝固,不會再變,一直保持這個結果。這時就稱爲 Resolved(已定型)。就算改變已經發生,再對Promise添加回調函數,也會獲得這個結果。 這與事件(event)徹底不一樣,事件的特色就是:若是錯過了,再去監聽是得不到結果的。app

Promise 缺點:異步

1 沒法取消。一旦創建就會當即執行,沒法中途取消。async

2 若是不設置回調函數,Promise內部報錯不會拋出到外面。異步編程

3 當處於Pending狀態,沒法得知目前進展到哪一階段(是剛剛開始,仍是即將完成?)

基本用法

var promise = new Promise((resolve,reject) => {
    // ...
    
    if(/* .....*/) {
        resolve(value)
    } else {
        reject(error)
    }
})
複製代碼

resolve做用是將Promise狀態由 未完成編程成功,在異步操做成功時調用,並將異步操做結果做爲參數傳播出去。

reject函數做用是,將Promise狀態從未完成變爲失敗,在異步操做失敗時調用,並將異步操做失敗報出的錯誤傳遞出去。

Promise 實例生成後,能夠調用then,指定 resolved 和 rejected 狀態的回調函數。

promise.then(function(val){
    // success
}, function(error) {
    // failure
})
複製代碼

then方法接受兩個回調函數做爲參數。第一個是 Promise 對象的狀態變爲Resolved時調用,第二個回調是Promise變爲Rejected時調用。第二個函數時可選的。

function timeout(ms){
    return new Promise((resolve,reject) => {
        setTimeout(resolve,ms,'done')
    })
}
timeout(100).then((value) => {
    console.log(value)
})
複製代碼

Promise 新建好以後就會當即執行。

let promise = new Promise(function(resolve,reject) {
    console.log('Promise');
    resolve();
})

promise.then(function(){
    console.log('Resolved')
})

console.log('Hi')
// Promise
// Hi
// Resolves
複製代碼

異步加載圖片:

function loadImgAsync(url) {
    return new Promise(function(resolve,reject) {
        var img = new Image()
        img.onload = function(){
            resolve(img)
        }
        img.onerror = function(){
            reject(new Error('Could not load img at ' + url))
        }
        img.src = url
    })
}
複製代碼

promise 實現 AJAX

var getJSON = function (url) {
    var promise = new Promise((resolve,reject)=> {
        var client = new XMLHttpRequest()
        client.open('GET',url)
        client.onreadystatechange = handle
        client.responseType = 'json'
        client.setRequestHeader("Accept","application/json")
        client.send()
        
        function handler(){
            if(this.readyState!==4){
                return 
            }
            if(this.status == 200){
                resolve(this.response)
            } else {
                reject(new Error(this.statusText))
            }
        }
    })
    return promise;
}

getJSON("/post.json").then((json)=>{
    console.log('Contents ' + json)
}, function(error){
    console.log(error)
})
複製代碼
var p1 = new Promise(function (resolve,reject){
    setTimeout(()=>{
        reject(new Error('fail'),3000)
    })
})

var p2 = new Promise(function(resolve,reject){
    setTimeout(()=>{
        resolve(p1)
    },1000)
})

p2
.then(rsult => console.log(result))
.catch(error => console.log(error))
// Error: fail
複製代碼

通常來講,調用resolve 或者 reject 之後,Promise的使命就已經完成了,後面的操做應該放到 then ,而不該該直接寫到 resolve 或者 reject 後面,因此最好在它們以前 添加 return 語句,這樣就不會產生意外。

new Promise((resolve,reject) => {
    return resolve(1);
    // 後面不會執行
    console.log(2)
})
複製代碼

Promise.prototype.then()

Promise實例具備 then 方法,做用是 爲 Promise 實例添加狀態改變時的回調函數。

then 方法 返回一個新的 Promise 實例(不是原來那個Promise),所以 可使用 鏈式寫法,then 後面 再 添加 另一個 then。

getJSON("/posts.json").then(function(json){
    return json.post
}).then(function(post){
    // ...
})
複製代碼

鏈式調用的 then 能夠指定 一組按照次序調用的回調函數。 前一個回調函數可能返回的仍是一個Promise,後一個回調函數就會等待該Promise 對象的狀態發生變化,在被調用。

getJSON("/post/1.json").then(function(post){
    return getJSON(post.commentUrl)
}).then(function funcA(comments) {
    console.log("resolved")
},function funcB(error){
    console.log(error)
})
複製代碼

Promise.prototype.catch()

這個方法 是 .then(null, rejection) 的別名,指定發生錯誤時的回調。

getJSON('/posts.json').then(function(posts){
    // ...
}).catch(function(error){
    console.log("error " + error )
})
複製代碼

Promise 在 resolve 語句以後 拋出錯誤,並不會被捕獲,等於沒有拋出。由於Promise狀態一旦發生改變,就會永久保存這個狀態,不會改變了。

Promise 對象錯誤具備「冒泡」性質,會一直向後傳遞,直到捕獲到爲止,錯誤總會被下一個catch捕獲

通常不要在 then 方法中定義 Rejected 狀態的回調函數,而應老是使用catch方法。

var someAsyncThing = function(){
    return new Promise((resolve,reject)=>{
    // 報錯 x 未聲明
        resolve(x + 2)
    })
}
someAsyncThing().then(()=>{
    return someotherAsyncThing()
}).catch(error => {
    console.log('oh no' , error)
    // 報錯 y 沒有聲明
    y + 2
}).then(()=>{
    console.log('carry on')
})
// oh no [ReferenceError: x is not defined ]
複製代碼

catch方法拋出一個錯誤,後面若是沒有catch,致使這個錯誤不會被捕獲,也不會傳遞到外層。

能夠用 第二個 catch 方法捕獲 前一個catch 方法拋出的錯誤。

someAsyncThing().then(()=>{
    return someotherAsyncThing()
}).catch(error => {
    console.log('oh no' , error)
    // 報錯 y 沒有聲明
    y + 2
}).catch(error=>{
    console.log('carry on ', error)
})
複製代碼

Promise.all()

將多個 Promise 實例 包裝成一個新的 Promise實例。

var p = Promise.all([p1, p2, p3])

p 狀態 由 p1,p2,p3 決定,分兩種狀況。

1 只有p1,p2,p3 狀態都變成 fulfilled ,p 的狀態 纔會變成 fulfilled,此時 p1,p2,p3 返回值組成一個數組,傳遞給 p 的回調。

2 只要 p1,p2,p3 有一個被Rejected,p 狀態也會變成 Rejected,此時,第一個被 Rejected 的實例返回值會傳遞給p的回調函數。

var promises = [2,3,5,7,11,13].map(function(id) {
    return getJSON('/post/'+id+'.json')
})

promise.all(promises).then(posts=>{
    //...
}).catch(reason=>{
    //...
})
複製代碼

另外一個例子

const databasePromise = connectDatabase()
const bookPromise = databasePromise.then(findAllBooks)
const userPromise = databasePromise.then(getCurrentUser)
Promise
.all([bookPromise,userPromise])
.then(([books,user])=>{
    //...
})
複製代碼

若是做爲參數的Promise自身定義了catch方法,那麼被rejected大時並不會觸發 Promise.all 的 catch 方法

Promise.race()

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

只要 p1,p2,p3中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的Promise 實例 返回值就是傳遞給p 的回調函數。

const p = Promise.race([
    fetch('/xxx'),
    new Promise((resolve,reject)=>{
        setTimeout(()=>{
            reject(new Error('time out'))
        },5000)
    })
])
p.then(res=>{
    console.log(res)
})
.catch(error => {
    console.log(error)
})
複製代碼

附加方法

done()

不管Promise對象的回調鏈以then方法仍是catch結尾,最後一個方法拋出的錯誤均可能沒法捕捉到(Promise內部的錯誤不會冒泡的全局)所以,提供一個 done 方法,老是處於回調鏈尾端,保證拋出任何可能的錯誤。

asyncFunc()
.then()
.catch()
.then()
.done()
複製代碼

實現

Promise.prototype.done = function(onFulilled, onRejected){
    this.then(onFulilled, onRejected)
    .catch(function(resason){
        setTimeout(() => {throw reason},0)
    })
}
複製代碼

finally()

finally方法用於指定無論Promise對象最後狀態如何都會執行的操做。與 done 最大的區別在於 他接受一個不普通的回調函數做爲參數,該函數無論怎樣都必須執行。

實現

Promise.prototype.finally = function(callback){
    let P = this.constructotr
    return this.then(
        value => P.resolve(callback()).then(()=>value),
        reason => P.resolve(callback()).then(()=> {throw reason})
    )
}
複製代碼
相關文章
相關標籤/搜索