Promise —— 解決回調地獄html
Promise鏈式調用html5
Promise異常處理node
Promise.prototype.catch()(推薦)ajax
Promise靜態方法編程
類型轉換 —— Promise.resolve()json
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
爲了不這個問題。CommonJS
社區提出了Promise
的規範,ES6
中稱爲語言規範。
Promise
是一個對象,用來表述一個異步任務執行以後是成功仍是失敗。
new Promise( function(resolve, reject) {…} );
new Promise(fn)
返回一個Promise
對象在
fn
中指定異步等處理
- 處理結果正常的話,調用
resolve
(處理結果值)- 處理結果錯誤的話,調用
reject
(Error
對象)
Promise
內部是有狀態的 (pending、fulfilled、rejected) ,Promise
對象根據狀態來肯定執行哪一個方法。Promise
在實例化的時候狀態是默認pending
的,
- 當異步操做是完成的,狀態會被修改成
fulfilled
- 若是異步操做遇到異常,狀態會被修改成
rejected
。不管修改成哪一種狀態,以後都是不可改變的。
返回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
去封裝一個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) })
本質上也是使用回調函數的方式去定義異步任務結束後所須要執行的任務。這裏的回調函數是經過then
方法傳遞過去的
Promise
最多見的誤區。要使用promise
的鏈式調用的方法儘量保證異步任務的扁平化。promise
對象then
方法,返回了全新的promise
對象。能夠再繼續調用then
方法,若是return
的不是promise
對象,而是一個值,那麼這個值會做爲resolve
的值傳遞,若是沒有值,默認是undefined
then
方法就是在爲上一個then
返回的Promise
註冊回調then
方法中回調函數的返回值會做爲後面then
方法回調的參數Promise
,那後面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
異常處理有如下幾種方法:
catc
h是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()
返回的promise
進行處理,不過第一個promise
的報錯也順延到了catch
中then
的第二個參數形式,只能捕獲第一個promise
的報錯,若是當前then
的resolve
函數處理中有報錯是捕獲不到的。因此.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
事件,處理那些代碼中沒有被手動捕獲的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.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
方法,傳入成功和失敗的回調,那麼在後面執行的時候,也是能夠按照promise
的then
來拿到。
(這個then方法,實現了一個thenable的接口,便可以被then的對象)
promise
庫轉化成promise
對象Promise.reslove({ then: function(onFulfilled, onRejected) { onFulfilled('foo') } }) .then(function (value) { console.log(value) // foo })
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(error)
是和 Promise.resolve(value)
相似的靜態方法,是 new Promise()
方法的快捷方式。
建立一個必定是失敗的promise
對象
Promise.reject(new Error('出錯了')) //等同於 new Promise(function (resolve) { reject(new Error('出錯了')) })
若是須要同時進行多個異步任務,使用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(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
以後才加入進去的,目的是爲了提升總體的響應能力
咱們目前絕大多數異步調用都是做爲宏任務執行,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
的東西以後再進一步探討。
說實話這個是最近比較複雜的一個筆記了,給本身點個贊,標個特殊標記。