ES6(十一)—— Promise(更優的異步編程解決方案)

目錄

  • 說到Promise就不得不說道說道這 —— 回調地獄
  • Promise —— 解決回調地獄html

    • Promise語法規範
    • Promise的狀態
    • Promise基本用法
    • Promise初體驗
    • Promise的本質
    • Promise鏈式調用html5

      • 常見誤區
      • 鏈式調用的理解
  • Promise.prototype.then()
  • Promise異常處理node

    • then中回調的onRejected方法
    • Promise.prototype.catch()(推薦)ajax

      • .catch形式和前面then裏面的第二個參數的形式,二者異常捕獲的區別:
    • 全局對象上的unhandledrejection事件
  • Promise靜態方法編程

    • 類型轉換 —— Promise.resolve()json

      • 使用場景
    • Promise.reject()
    • 數據聚合 —— Promise.all()
    • 競爭 —— Promise.race()
  • Promise執行時序 —— 宏任務 vs 微任務
  • 深度剖析:手寫一個Promise源碼
  • ES6-ES10學習版圖

說到Promise就不得不說道說道這 —— 回調地獄

a => b => c => dsegmentfault

回調層數越深,那麼回調的維護成本越高api

//異步加載函數
function loadScript (src, callback) {
    let script = document.createElement('script')
    script.src = src
    script.onload = () => {
        callback()
    }
    document.head.append(script)
}

function test () {
    console.log('test')
}
loadScript('./1.js', test)

// 1
// test

若是有三個這樣的方式回調數組

function loadScript (src, callback) {
    let script = document.createElement('script')
    script.src = src
    script.onload = () => {
        callback(src)
    }
    document.head.append(script)
}

function test (name) {
    console.log(name)
}
loadScript('./1.js', function (script) {
    console.log(script)
    loadScript('./2.js', function (script) {
        console.log(script)
        loadScript('./3.js', function (script) {
            console.log(script)
            //...
        })
    })
})

// 1
// ./1.js
// 2
// ./2.js
// 3
// ./3.js

Promise —— 解決回調地獄

雖然回調函數是全部異步編程方案的根基。可是若是咱們直接使用傳統回調方式去完成複雜的異步流程,就會沒法避免大量的回調函數嵌套。致使回調地獄的問題。promise

爲了不這個問題。CommonJS社區提出了Promise的規範,ES6中稱爲語言規範。

Promise是一個對象,用來表述一個異步任務執行以後是成功仍是失敗。

Promise語法規範

new Promise( function(resolve, reject) {…} );

  • new Promise(fn) 返回一個Promise 對象
  • fn中指定異步等處理

    • 處理結果正常的話,調用resolve(處理結果值)
    • 處理結果錯誤的話,調用reject(Error對象)

Promise的狀態

Promise 內部是有狀態的 (pending、fulfilled、rejected)Promise 對象根據狀態來肯定執行哪一個方法。Promise 在實例化的時候狀態是默認 pending 的,

  • 當異步操做是完成的,狀態會被修改成 fulfilled
  • 若是異步操做遇到異常,狀態會被修改成 rejected

不管修改成哪一種狀態,以後都是不可改變的。

Promise基本用法

返回resolve

const promise = new Promise((resolve, reject) => {
  resolve(100)
})

promise.then((value) => {
  console.log('resolved', value) // resolve 100
},(error) => {
  console.log('rejected', error)
})

返回reject

const promise = new Promise((resolve, reject) => {
  reject(new Error('promise rejected'))
})

promise.then((value) => {
  console.log('resolved', value)
},(error) => {
  console.log('rejected', error)
  // rejected Error: promise rejected
  //  at E:\professer\lagou\Promise\promise-example.js:4:10
  //  at new Promise (<anonymous>)
})

即使promise中沒有任何的異步操做,then方法的回調函數仍然會進入到事件隊列中排隊。

Promise初體驗

使用Promise去封裝一個ajax的案例

function ajax (url) {
  return new Promise((resolve, rejects) => {
    // 建立一個XMLHttpRequest對象去發送一個請求
    const xhr = new XMLHttpRequest()
    // 先設置一下xhr對象的請求方式是GET,請求的地址就是參數傳遞的url
    xhr.open('GET', url)
    // 設置返回的類型是json,是HTML5的新特性
    // 咱們在請求以後拿到的是json對象,而不是字符串
    xhr.responseType = 'json'
    // html5中提供的新事件,請求完成以後(readyState爲4)纔會執行
    xhr.onload = () => {
      if(this.status === 200) {
        // 請求成功將請求結果返回
        resolve(this.response)
      } else {
        // 請求失敗,建立一個錯誤對象,返回錯誤文本
        rejects(new Error(this.statusText))
      }
    }
    // 開始執行異步請求
    xhr.send()
  })
}

ajax('/api/user.json').then((res) => {
  console.log(res)
}, (error) => {
  console.log(error)
})

Promise的本質

本質上也是使用回調函數的方式去定義異步任務結束後所須要執行的任務。這裏的回調函數是經過then方法傳遞過去的

Promise鏈式調用

常見誤區
  • 嵌套使用的方式是使用Promise最多見的誤區。要使用promise的鏈式調用的方法儘量保證異步任務的扁平化。
鏈式調用的理解
  • promise對象then方法,返回了全新的promise對象。能夠再繼續調用then方法,若是return的不是promise對象,而是一個值,那麼這個值會做爲resolve的值傳遞,若是沒有值,默認是undefined
  • 後面的then方法就是在爲上一個then返回的Promise註冊回調
  • 前面then方法中回調函數的返回值會做爲後面then方法回調的參數
  • 若是回調中返回的是Promise,那後面then方法的回調會等待它的結束

Promise.prototype.then()

promise對象就能夠調用.then(),是promise原型對象上的方法

promise.then(onFulfilled,onRejected);

onFulfilled 參數對應 resolve,處理結果值,必選

onRejected 參數對應 reject,Error對象,可選

Promise 對象會在變爲 resolve 或者 reject 的時候分別調用相應註冊的回調函數。

  • handler 返回一個正常值的時候,這個值會傳遞給 Promise 對象的 onFulfilled 方法。
  • 定義的 handler 中產生異常的時候,這個值則會傳遞給 Promise 對象的 onRejected 方法。

這兩個參數都是兩個函數類型,若是這兩個參數是非函數或者被遺漏,就忽略掉這兩個參數了,返回一個空的promise對象。

// 普通的寫法會致使有不穩定輸出
function loadScript (src) {
    //resolve, reject是能夠改變Promise狀態的,Promise的狀態是不可逆的
    return new Promise((resolve, reject) => {
        let script = document.createElement('script')
        script.src = src
        script.onload = () => resolve(src) //fulfilled,result
        script.onerror = (err) => reject(err) //rejected,error
        document.head.append(script)
    })
}

loadScript('./1.js')
    .then(loadScript('./2.js'))
    .then(loadScript('./3.js'))
    
//不穩定輸出    
// 1
// 2
// 3
----------------------------------------------------------------------------
// 若是把加載2和3的放在1的then方法中
function loadScript (src) {
    //resolve, reject是能夠改變Promise狀態的,Promise的狀態是不可逆的
    return new Promise((resolve, reject) => {
        let script = document.createElement('script')
        script.src = src
        script.onload = () => resolve(src) //fulfilled,result
        script.onerror = (err) => reject(err) //rejected,error
        document.head.append(script)
    })
}

loadScript('./1.js')
    .then(() => {
        loadScript('./2.js')
    }, (err) => {
        console.log(err)
    }).then( () => {
        loadScript('./3.js')
    }, (err) => {
        console.log(err)
    })
    
// 穩定輸出
// 1
// 不穩定輸出
// 2
// 3
// ----------------------------------------------
//可是若是中間有錯誤的時候,下面的3仍是會執行。
loadScript('./1.js')
    .then(() => {
        loadScript('./4.js')
    }, (err) => {
        console.log(err)
    }).then( () => {
        loadScript('./3.js')
    }, (err) => {
        console.log(err)
    })

// 1
// 報錯
// 3
// 不符合題意,若是是報錯以後,3不該該執行
// -------------------------------------------------------
loadScript('./1.js')
    .then(() => {
        return loadScript('./2.js')
    }, (err) => {
        console.log(err)
    }).then(() => {
        return loadScript('./3.js')
    }, (err) => {
        console.log(err)
    })
// 不加返回值,依舊是一個空的promise對象,沒法用resolve, reject影響下一步.then()的執行
// 添加返回值以後就能夠穩定輸出
// 1
// 2
// 3

Promise異常處理

異常處理有如下幾種方法:

then中回調的onRejected方法

Promise.prototype.catch()(推薦)

catch是promise原型鏈上的方法,用來捕獲reject拋出的一場,進行統一的錯誤處理,使用.catch方法更爲常見,由於更加符合鏈式調用

p.catch(onRejected);
ajax('/api/user.json')
  .then(function onFulfilled(res) {
    console.log('onFulfilled', res)
  }).catch(function onRejected(error) {
    console.log('onRejected', error)
  })
  
// 至關於
ajax('/api/user.json')
  .then(function onFulfilled(res) {
    console.log('onFulfilled', res)
  })
  .then(undefined, function onRejected(error) {
    console.log('onRejected', error)
  })
.catch形式和前面then裏面的第二個參數的形式,二者異常捕獲的區別:
  • .catch()是對上一個.then()返回的promise進行處理,不過第一個promise的報錯也順延到了catch
  • then的第二個參數形式,只能捕獲第一個promise的報錯,若是當前thenresolve函數處理中有報錯是捕獲不到的。

因此.catch是給整個promise鏈條註冊的一個失敗回調。推薦使用!!!!

function loadScript (src) {
    //resolve, reject是能夠改變Promise狀態的,Promise的狀態是不可逆的
    return new Promise((resolve, reject) => {
        let script = document.createElement('script')
        script.src = src
        script.onload = () => resolve(src) //fulfilled,result
        script.onerror = (err) => reject(err) //rejected,error
        document.head.append(script)
    })
}


loadScript('./1.js')
    .then(() => {
        return loadScript('./2.js')
    }).then(() => {
        return loadScript('./3.js')
    })
    .catch(err => {
        console.log(err)
    })
// throw new Error 不要用這個方法,要用catch和reject,去改變promise的狀態的方式

全局對象上的unhandledrejection事件

還能夠在全局對象上註冊一個unhandledrejection事件,處理那些代碼中沒有被手動捕獲的promise異常,固然並不推薦使用

更合理的是:在代碼中明確捕獲每個可能的異常,而不是丟給全局處理

// 瀏覽器
window.addEventListener('unhandledrejection', event => {
  const { reason, promise } = event
  console.log(reason, promise)

  //reason => Promise 失敗緣由,通常是一個錯誤對象
  //promise => 出現異常的Promise對象

  event.preventDefault()
}, false)

// node
process.on('unhandledRejection', (reason, promise) => {
  console.log(reason, promise)

  //reason => Promise 失敗緣由,通常是一個錯誤對象
  //promise => 出現異常的Promise對象
})

Promise靜態方法

類型轉換 —— Promise.resolve()

靜態方法 Promise.resolve(value) 能夠認爲是 new Promise() 方法的快捷方式。

Promise.resolve(42)
//等同於
new Promise(function (resolve) {
  resolve(42)
})

若是接受的是一個promise對象,那麼這個對象會原樣返回

const promise2 = Promise.resolve(promise)
console.log(promise === promise2) // true

若是傳入的是一個對象,且這個對象也有一個then方法,傳入成功和失敗的回調,那麼在後面執行的時候,也是能夠按照promisethen來拿到。

(這個then方法,實現了一個thenable的接口,便可以被then的對象)

使用場景
  1. 能夠是把第三方模擬promise庫轉化成promise對象
Promise.reslove({
    then: function(onFulfilled, onRejected) {
        onFulfilled('foo')
    }
})
.then(function (value) {
    console.log(value) // foo
})
  1. 直接將數值轉換成promise對象返回
function test (bool) {
    if (bool) {
        return new Promise((resolve,reject) => {
            resolve(30) 
        })
    } else {
        return Promise.resolve(42)
    }
}
test(1).then((value) => {
    console.log(value)
})

Promise.reject()

Promise.reject(error) 是和 Promise.resolve(value) 相似的靜態方法,是 new Promise() 方法的快捷方式。

建立一個必定是失敗的promise對象

Promise.reject(new Error('出錯了'))
//等同於
new Promise(function (resolve) {
  reject(new Error('出錯了'))
})

數據聚合 —— Promise.all()

若是須要同時進行多個異步任務,使用promise靜態方法中的all方法,能夠把多個promise合併成一個promise統一去管理。

Promise.all(promiseArray);

  • Promise.all 生成並返回一個新的 Promise 對象,因此它可使用 Promise 實例的全部方法。參數傳遞promise數組中全部的 Promise 對象都變爲resolve的時候,該方法纔會返回, 新建立的 Promise 則會使用這些 promise 的值。
  • 參數是一個數組,元素能夠是普通值,也能夠是一個promise對象,輸出順序和執行順序有關,
  • 該函數生成並返回一個新的 Promise 對象,因此它可使用 Promise 實例的全部方法。參數傳遞promise數組中全部的 Promise 對象都變爲resolve的時候,該方法纔會返回完成。只要有一個失敗,就會走catch
  • 因爲參數數組中的每一個元素都是由 Promise.resolve 包裝(wrap)的,因此Paomise.all 能夠處理不一樣類型的 promose 對象。
var promise = Promise.all([
    // ajax函數是一個異步函數並返回promise,不須要關心哪一個結果先回來,由於是都完成以後整合操做
    ajax('/api/users.json'),
    ajax('/api/posts.json')
])

Promise.then(function(values) {
    console.log(values) //返回的是一個數組,每一個數組元素對應的是其promise的返回結果
}).catch(function(error) {
    console.log(error) // 只要有一個失敗,那麼就會總體失敗走到catch裏面
})

競爭 —— Promise.race()

Promise.race(promiseArray);

all同樣會接收一個數組,元素能夠是普通值也能夠是promise對象,和all不一樣的是,它只會等待第一個結束的任務

// 下面的例子若是request超過了500ms,那麼就會報超時錯誒,若是小於500ms,則正常返回。
const request = ajax('/api/posts.json')
const timeout = new Promise((resovle, reject) => {
    setTimeout(() => reject(new Error('timeout')), 500)
})

Promise.race([
    request,
    timeout
])
.then(value => {
    console.log(value)
})
.catch(error => {
    console.log(error)
})

Promise執行時序 —— 宏任務 vs 微任務

執行順序 : 宏任務 => 微任務 => 宏任務

微任務promise以後才加入進去的,目的是爲了提升總體的響應能力

咱們目前絕大多數異步調用都是做爲宏任務執行, promise的回調 & MutationObserver & node中的 process.nextTick會做爲微任務執行

下面的例子,當前宏任務當即執行,then是微任務會延後執行,setTImeout是異步的一個宏任務也會延後執行。當前宏任務執行完畢以後,微任務會先執行完畢以後下一個宏任務纔會執行。

console.log('global start')

setTimeout(() => {
    console.log('setTimeout')
}, 0)
Promise.resolve()
    .then(( => {
        console.log('promise')
    }))
    .then(( => {
        console.log('promise2')
    }))
    .then(( => {
        console.log('promise3')
    }))

console.log('global end')

// global start
// global end
// promise
// promise2
// promise3
// setTimeout

具體的牽扯到eventLoop的東西以後再進一步探討。

深度剖析:手寫一個Promise源碼

深度剖析:手寫一個Promise源碼

ES6-ES10學習版圖

說實話這個是最近比較複雜的一個筆記了,給本身點個贊,標個特殊標記。

相關文章
相關標籤/搜索