在以前翻博客時,看到promise,又重讀了一邊,忽然發現理解很淺,記的筆記也不是很好理解,又從新學習promise,加深理解,學以至用
在promise出來以前,js經常使用解決異步方式都是採用回調函數方式,可是若是需求過多,會造成一系列的回調函數,俗稱:回調地獄。致使後期閱讀和維護代碼特別麻煩。因此es6的Promise就是爲了解決這個麻煩而出來的新對象,以前早就存在,ES6將其寫進了語言標準,統一了用法,原生提供了Promise對象。javascript
Promise對象是爲了簡化異步編程。解決回調地獄狀況。
Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。重點是取決與這個事件以後的一系列動做,then()或catch()的等等。
從語法上說,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise對象用於延遲(deferred) 計算和異步(asynchronous ) 計算。一個Promise對象表明着一個還未完成,但預期未來會完成的操做。這樣表示了一旦用了promise對象,就不能退出,直到出現結果爲止(resloved或rejected)
Promise是一個對象,能夠用構造函數來建立一個Promise實例。java
let promise = new Promise((resolve, reject) =>{ // .... some coding if (true){ // 異步操做成功 resolve(value); } else { reject(error); } }) promise.then(value=>{ // 成功的回調函數 }, error=>{ // 失敗後的回調函數 }) console.log(typeof promise) // object
params:傳參是一個回調函數。這個回調函數有兩個參數resolve和reject。git
成功了的執行
)失敗了的執行
)// 成功時 let promise = new Promise((resolve, reject) =>{ console.log('開始') if (2 > 1){ // 異步操做成功 resolve({name:'peter',age:25}); } else { reject(error); } }) promise.then(value=>{ // 成功的回調函數 console.log(value) }, error=>{ // 失敗後的回調函數 console.log(error) }) // 開始 // {name: "peter", age: 25}
// 失敗時 let promise = new Promise((resolve, reject) =>{ console.log('開始') if (2 > 3){ // 異步操做成功 resolve(a); } else { reject('未知錯誤'); } }) promise.then(value=>{ // 成功的回調函數 console.log(value) }, error=>{ // 失敗後的回調函數 console.log(error) }) // 開始 // 未知錯誤
ps:Promise實例化一個對象後,會當即實行。es6
new Promise((resolve, reject)=>console.log('promise')); console.log('123'); // promise // 123
這個結果發現,先執行promise後執行123。github
若是改變已經發生了,你再對Promise對象添加回調函數,也會當即獲得這個結果。這與事件(Event)徹底不一樣,事件的特色是,若是你錯過了它,再去監聽,是得不到結果的
ajax
promise先按順序實行完promise實例中方法再實行then中的resolve或者reject。編程
let promise = new Promise((resolve, reject)=>{ console.log('promise') if (2 > 1){ // 異步操做成功 resolve({name:'peter',age:25}); } else { reject(error); } console.log('end') }) promise.then( value=>{ console.log(value) }, error=>{ console.log(error) } ) // promise // end // {name: "peter", age: 25}
ajax是最多見的異步操做方式,那麼用promise封裝Ajax的例子json
const getJSON = function (url) { const promise = new Promise(function (resolve, reject) { const handler = function () { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; const client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); }); return promise; }; getJSON("xxxxx").then(function (value) { console.log('Contents: ' + value); }, function (error) { console.error('出錯了', error); });
then() 爲 Promise 實例添加狀態改變時的回調函數,上面已經提起過。
params解釋:segmentfault
一般狀況下,then方法做爲成功時的回調方法,catch方法做爲失敗時回調方法。catch()在後面,能夠理解爲then方法中的reject參數數組
let promise = new Promise((resolve, rejected)=>{ if(2<3){ resolve() }else{ rejected() } }) promise.then(resolve=>{ console.log('right') }).catch(reject=>{ console.log('error') })
ps: then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。
var aPromise = new Promise(function (resolve) { resolve(100); }); var thenPromise = aPromise.then(function (value) { console.log(value); }); var catchPromise = thenPromise.catch(function (error) { console.error(error); }); console.log(aPromise !== thenPromise); // => true console.log(thenPromise !== catchPromise);// => true
因此每個then()方法就是一個新promise對象。所以能夠採用鏈式寫法,即then方法後面再調用另外一個then方法。這樣必需要傳一個參數過去。
promise的鏈式編程,就是第一個的Promise實例的返回的值做爲下一個Promise實例的參數。
function start() { return new Promise((resolve, reject) => { resolve('start'); }); } start() .then(data => { // promise start console.log(data); return Promise.resolve(1); // 1 }) .then(data => { // promise 1 console.log(data); }) // start // 1
從上面例子可知:
catch()和then()都是掛載在promise對象的原型上的。
Promise.prototype.catch
方法是promise.then(null, rejection
)或promise.then(undefined, rejection)
的別名,用於指定發生錯誤時的回調函數。
通常是等價於:(在遇到失敗的狀況下)
Promise.catch()
<=> promise.then(null,e=>reject())
若是異步操做拋出錯誤,狀態就會變爲rejected,就會調用catch方法指定的回調函數,處理這個錯誤。另外,then方法指定的回調函數,若是運行中拋出錯誤,也會被catch方法捕獲。
const promise = new Promise(function(resolve, reject) { throw new Error('test'); }); promise.catch(function(error) { console.log(error); }); // Error: test
Promise 對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤老是會被下一個catch語句捕獲。
function throwError(value) { // 拋出異常 throw new Error(value); } // <1> onRejected不會被調用 function badMain(onRejected) { return Promise.resolve(42).then(throwError, onRejected); } // <2> 有異常發生時onRejected會被調用 function goodMain(onRejected) { return Promise.resolve(42).then(throwError).catch(onRejected); } // 運行示例 badMain(function(){ console.log("BAD"); }); goodMain(function(){ console.log("GOOD"); }); // GOOD
在上面的代碼中, badMain 是一個不太好的實現方式(但也不是說它有多壞), goodMain 則是一個能很是好的進行錯誤處理的版本。
爲何說 badMain 很差呢?,由於雖然咱們在 .then 的第二個參數中指定了用來錯誤處理的函數,但實際上它卻不能捕獲第一個參數 onFulfilled 指定的函數(本例爲 throwError )裏面出現的錯誤。也就是說,這時候即便 throwError 拋出了異常,onRejected 指定的函數也不會被調用(即不會輸出"BAD"字樣)。
與此相對的是, goodMain 的代碼則遵循了 throwError→onRejected 的調用流程。 這時候 throwError 中出現異常的話,在會被方法鏈中的下一個方法,即 .catch 所捕獲,進行相應的錯誤處理。
.then 方法中的onRejected參數所指定的回調函數,實際上針對的是其promise對象或者以前的promise對象,而不是針對 .then 方法裏面指定的第一個參數,即onFulfilled所指向的對象,這也是 then 和 catch 表現不一樣的緣由。(詳見Javascript Promise 迷你版)
這個是從別人的博客拿來的代碼和解釋,了那麼多,總結爲,catch可以捕獲它以前的異常,而在then()方法中第二個參數是沒辦法捕獲到的,由於實行了resolve方法。
看字面量的意思,是返回一個成功的promise實例。
Promise.resolve()
<=> new Promise((resolve,rejected)=>resolve())
最多見的就是將不是promise對象的異步操做轉化爲promise對象。
該方法有四個參數:
javascript let thenable = { then: function(resolve, reject) { resolve(42); } }; var promise = Promise.resolve(thenable) .then(value=>console.log(value));// 42
javascript var str = '17號' Promise.resolve(str).then(value=>console.log(value)) // 17號
返回一個新的 Promise 實例,該實例的狀態爲rejected。用法和resolve同樣,可是都是以失敗返回結果
Promise.reject()
<=> new Promise((resolve,reject) = >reject())
ps:Promise.reject()方法的參數,會原封不動地做爲reject的理由,變成後續方法的參數。這一點與Promise.resolve方法不一致。
const thenable = { then(resolve, reject) { reject('出錯了'); } }; Promise.reject(thenable) .catch( e=> { console.log(e) }) // 返回的是thenable對象
Promise.all 接收一個 promise對象的數組做爲參數,當這個數組裏的全部promise對象所有變爲resolve或reject狀態的時候,它纔會去調用 .then() 方法。
// `delay`毫秒後執行resolve function timerPromisefy(delay) { return new Promise(function (resolve) { setTimeout(function () { resolve(delay); }, delay); }); } var startDate = Date.now(); // 全部promise變爲resolve後程序退出 Promise.all([ timerPromisefy(1), timerPromisefy(32), timerPromisefy(64), timerPromisefy(128) ]).then(function (values) { console.log(Date.now() - startDate + 'ms'); console.log(values); }); // 129ms // 1,32,64,128
從上述結果能夠看出,傳遞給 Promise.all 的promise並非一個個的順序執行的,而是同時開始、並行執行的。
假設法:若是這些promise所有串行處理的話,那麼須要 等待1ms → 等待32ms → 等待64ms → 等待128ms ,所有執行完畢須要225ms的時間。但實際上不是,而是129ms左右。
和Promise.all()方法同樣,參數是一個數組,可是只要有一個promise對象更改狀態時就實行下一步。
// `delay`毫秒後執行resolve function timerPromisefy(delay) { return new Promise(function (resolve) { setTimeout(function () { resolve(delay); }, delay); }); } // 任何一個promise變爲resolve或reject 的話程序就中止運行 Promise.race([ timerPromisefy(1), timerPromisefy(32), timerPromisefy(64), timerPromisefy(128) ]).then(function (value) { console.log(value); // => 1 });
上面的例子是1秒後就resolve了,因此直接then()了。
該方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。不管resolve仍是reject都會實行的操做,不依賴其餘的操做。按照執行順序。
function promise(){ return new Promise((resolve, reject) => { resolve('success'); }) }; promise().then(data => { console.log(data) return Promise.reject('fail') }).finally(() => { console.log('end') }).catch(data =>{ console.log(data) }) // success // end // fail
從上面的例子可知,是按照promise的實行順序執行的,在then()中,要求返回一個失敗的狀態,可是卻沒先實行失敗的方法,而是按照順序實行了finally方法。
Promise 對象的回調鏈,無論以then方法或catch方法結尾,要是最後一個方法拋出錯誤,都有可能沒法捕捉到(由於 Promise 內部的錯誤不會冒泡到全局)。所以,咱們能夠提供一個done方法,老是處於回調鏈的尾端,保證拋出任何可能出現的錯誤。
Promise.prototype.done = function (resolve, reject) { this.then(resolve, reject) .catch( function (reason) { // 拋出一個全局錯誤 setTimeout( () => { throw reason }, 0); }); } // 使用示例 var p = new Promise( (resolve, reject) => { resolve('p'); }); p.then(ret => { console.log(ret); return 'then1'; }).catch( err => { console.log(err.toString()); }).then( ret => { console.log(ret); return 'then2'; }).then( ret => { console.log(ret); x + 2; }).done();
該例子參考別人的例子。發現到最後直接拋出了 'Uncaught ReferenceError: x is not defined'。說明最後一個then實行時會拋出異常,也能夠相似於catch方法吧。
總結來講Promise其實就是作了一件事情,那就是對異步操做進行了封裝,而後能夠將異步操做以同步的流程表達出來,避免了層層嵌套的回調地獄,提供統一的接口方法,使得控制異步操做更加容易,可是也有必定的缺點,promise一旦沒肯定狀態,是無法終止的,一樣的,也沒法取消promise。
若是本文有什麼不對的地方,歡迎指出,謝謝,你們一塊兒進步加油。我把筆記放到github了,若是滿意的話給個star。