1. 程式人生 > 其它 >JavaScript的Promise物件

JavaScript的Promise物件

1.Promise物件

Promise是非同步程式設計的一種解決方案,ES6原生提供了Promise物件。Promise物件代表了未來將要發生的事件,用來傳遞非同步操作的訊息。

Promise物件有兩個特點

  (1)物件的狀態不受外界影響。Promise物件代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。

  (2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise物件的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise物件添加回調函式,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

有了Promise物件,就可以將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函式。此外,Promise物件提供統一的介面,使得控制非同步操作更加容易。

Promise也有一些缺點。首先,無法取消Promise,一旦新建它就會立即執行,無法中途取消。其次,如果不設定回撥函式,Promise內部丟擲的錯誤,不會反應到外部。第三,當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

2.Promise的基本用法

ES6 規定,Promise物件是一個建構函式,用來生成Promise例項。

建立一個promise物件,可以使用new來呼叫Promise的構造器來進行例項化:

var promise = new Promise(function(resolve, reject) {
    // 非同步處理
    if(/* 非同步操作成功 */){
        resolve(value);
    } else {
        reject(error);
    }
});

Promise建構函式接受一個函式作為引數,該函式的兩個引數分別是resolve和reject。它們是兩個函式,由 JavaScript 引擎提供,不用自己部署。

resolve函式的作用是,將Promise物件的狀態從“未完成”變為“成功”(即從 pending 變為 resolved),在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞出去;reject函式的作用是,將Promise物件的狀態從“未完成”變為“失敗”(即從 pending 變為 rejected),在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去。

Promise例項生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回撥函式。

//value和error分別是上面呼叫resolve(...)和reject(...)方法傳入的值
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

下面是一個Promise物件的例子

var promise = new Promise(function(resolve, reject) {
    console.log("1")
    setTimeout(function(){
        console.log("2")
        resolve()
    },1000)
});
console.log("3")
promise.then(function(){
    console.log("4")
})
console.log("5")

在上述程式碼中,控制檯列印的順序是1、3、5、2、4

因為Promise新建後就會立即執行,所以會首先列印1,然後程式碼遇到非同步程式碼會先將其新增到任務佇列,等同步任務執行完畢之後才執行非同步程式碼,所以之後會先依次列印3、5。setTimeout函式一秒後在控制檯列印2,並將Promise例項的狀態變為resolved,然後觸發then方法繫結的回撥函式,列印4。

下面是一個用Promise物件實現Ajax操作的例子

function ajax(URL) {
    return new Promise(function (resolve, reject) {
        var req = new XMLHttpRequest(); 
        req.open('GET', URL, true);
        req.onload = function () {
        if (req.status === 200) { 
                let data = JSON.parse(req.responseText);
                resolve(data['hitokoto']);
            } else {
                reject(new Error(req.statusText));
            } 
        };
        req.onerror = function () {
            reject(new Error(req.statusText));
        };
        req.send(); 
    });
}
var URL = "https://v1.hitokoto.cn/?c=k"; 
ajax(URL).then(function onFulfilled(value){
    document.write( value); 
}).catch(function onRejected(error){
    document.write('錯誤:' + error); 
});

上面程式碼中,resolve 方法和 reject 方法呼叫時,都帶有引數。它們的引數會被傳遞給回撥函式。reject 方法的引數通常是 Error 物件的例項,而 resolve 方法的引數除了正常的值以外,還可能是另一個 Promise 例項,比如像下面這樣。

var p1 = new Promise(function(resolve, reject){
  // ... some code
});
 
var p2 = new Promise(function(resolve, reject){
  // ... some code
  resolve(p1);
})

上面程式碼中,p1和p2都是 Promise 的例項,但是p2的resolve方法將p1作為引數,即一個非同步操作的結果是返回另一個非同步操作。

注意,這時p1的狀態就會傳遞給p2,也就是說,p1的狀態決定了p2的狀態。如果p1的狀態是pending,那麼p2的回撥函式就會等待p1的狀態改變;如果p1的狀態已經是resolved或者rejected,那麼p2的回撥函式將會立刻執行。

一般來說,呼叫resolve或reject以後,Promise 的使命就完成了,後繼操作應該放到then方法裡面,而不應該直接寫在resolve或reject的後面。所以,最好在它們前面加上return語句,這樣就不會有意外。

new Promise((resolve, reject) => {
  return resolve(1);
  // 後面的語句不會執行
  console.log(2);
})