深刻理解es6-Promise對象

前言

    在以前翻博客時,看到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

  • resolve: 將Promise對象的狀態從「未完成」變爲「成功」(即從 pending 變爲 resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去。(簡單來講就是成功了的執行)
  • reject: 將Promise對象的狀態從「未完成」變爲「失敗」(即從 pending 變爲 rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。(簡單來講就是失敗了的執行)
  • promise以後then的參數:
    • 第一個參數是成功的回調函數,必選
    • 第二個參數是失敗的回調函數,可選
    // 成功時
    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的特色

  • 對象的狀態不受外界影響。Promise對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。
  • 一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。就是成功了就一直是成功的狀態fulfilled,失敗一直是失敗的狀態rejected。

若是改變已經發生了,你再對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);
});

Promise方法

promise.then()

then() 爲 Promise 實例添加狀態改變時的回調函數,上面已經提起過。
params解釋:segmentfault

  • 第一個參數是resolved狀態的回調函數, 必選
  • 第二個參數是rejected狀態的回調函數, 可選

一般狀況下,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

從上面例子可知:

  • start函數裏resolve裏傳了一個參數‘start’
  • 第一個then方法接受了start,而後return 一個成功的值 1
  • 第二個then方法接受上一個then傳來的值 1

Promise.catch()

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.resolve()

看字面量的意思,是返回一個成功的promise實例。
Promise.resolve() <=> new Promise((resolve,rejected)=>resolve())
最多見的就是將不是promise對象的異步操做轉化爲promise對象。
該方法有四個參數:

  • 無參數
    直接返回一個resolved狀態的 Promise 對象,所謂的字面量意思。
  • 參數是一個 Promise 實例
    Promise.resolve將不作任何修改、原封不動地返回這個實例。
  • 參數是一個thenable對象
    所謂的thenable對象指的就是具備then方法的對象,相似於類數組具備數組的length,但不是數組同樣。
    javascript let thenable = { then: function(resolve, reject) { resolve(42); } }; var promise = Promise.resolve(thenable) .then(value=>console.log(value));// 42
    上面的例子就是將具備then方法的thenable對象轉化爲promise對象,而且當即執行resolve方法。
  • 參數不是具備then方法的對象,或根本就不是對象
    若是參數是一個原始值,或者是一個不具備then方法的對象,則Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。
    javascript var str = '17號' Promise.resolve(str).then(value=>console.log(value)) // 17號

Promise.reject()

返回一個新的 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.all 接收一個 promise對象的數組做爲參數,當這個數組裏的全部promise對象所有變爲resolve或reject狀態的時候,它纔會去調用 .then() 方法。

  • 該方法的參數是一個數組
  • 該方法的參數數組是必須含有promise對象的數組
  • 只有數組中全部的promise對象都變成resolve或者reject才能進行下一步操做。
// `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.race()

和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()了。

Promsie.finally()

該方法用於指定無論 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.done()

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。

參考資料

ES6標準入門
Javascript Promise 迷你版
學習Promise

相關文章
相關標籤/搜索