1. 程式人生 > >入門Promise的正確姿勢

入門Promise的正確姿勢

說過 一般來說 完全 第一個 失敗 測試 也會 沒有 輸出

Promise是異步編程的一種解決方案,從語法上說,Promise是一個對象,從它可以獲取異步操作的消息。

Promise的基本用法

Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由JavaScript引擎提供。

  • resolve函數的作用是,將Promise對象的狀態從“未完成”變為“成功”(即從Pending變為Resolved),在異步操作成功時調用,並將異步操作的結果作為參數傳遞出去。
  • reject函數的作用是,將Promise對象的狀態從“未完成”變為“失敗”(即從Pending變為Rejected),在異步操作失敗時調用
    ,並將異步操作報出的錯誤作為參數傳遞出去。
  • then方法可以接受兩個回調函數作為參數。第一個回調函數是Promise對象的狀態變為Resolved時調用,第二個回調函數是Promise對象的狀態變為 Reject時調用
var promise = new Promise(
    //異步執行,Promise對象創建後會被立即執行
    function (resolve,reject) {
        //耗時很長的異步操作
  if(‘異步處理成功‘) {  
        resolve();    //數據處理成功時調用
  } else {
        reject();    
//數據處理失敗時調用 } } ) //Promise實例生成以後,可以用then方法分別指定Resolved狀態和Reject狀態的回調函數。 promise.then( function A() { //數據處理成功後執行 }, function B() { //數據處理失敗後執行 } )

下面我們舉一個簡單的例子來模擬一下異步操作成功和異步操作失敗函數的運行過程。

console.log(‘starting‘);
var promise = new Promise(function(resolve, reject) {  
    setTimeout(
function() { console.log("2秒後,我運行了"); resolve(‘異步操作成功了‘); //1 //reject(‘異步操作失敗了‘); //2
     //return ‘hello‘;       //3
}, 2000) }).then(function (value) { console.log(‘異步操作成功後執行我:‘,value); }, function (value) { console.log(‘異步操作失敗後執行我:‘,value); } ) console.log(‘我也運行了‘); // 上面的代碼中1處代碼的調用,輸出順序是: //starting //我也運行了
//2秒後,我運行了 // 異步操作成功後執行我: 異步操作成功了 // 上面的代碼中2處代碼的調用,輸出順序是: //starting //我也運行了
//2秒後,我運行了 // 異步操作失敗後後執行我: 異步操作失敗了


//上面的代碼中3處代碼的調用,輸出順序是:
//starting
//我也運行了
//2秒後,我運行了

知代碼3處的return ‘hello‘ 語句在新建的new Promise對象中並沒有被當作參數返回給then()函數內.那麽會不會返回給promise了呢?我們用一段代碼來測試一下

console.log(‘starting‘);
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒後,我運行了");
        resolve(‘異步操作成功了‘);     //1
        //reject(‘異步操作失敗了‘);    //2
        return ‘hello‘;
    }, 2000) 
    
})
promise.then(function (value) { 
    console.log(‘異步操作成功後執行我:‘,value);
},
function (value) {
    console.log(‘異步操作失敗後執行我:‘,value);
}
)
console.log(‘我也運行了‘);
console.log(promise);
setTimeout(function () {
    console.log(‘5秒後,我執行了‘);
    console.log(promise);
},5000);


//starting
//我也運行了
//Promise { pending }
  //[[PromiseStatus]]:"pending"
  //[[PromiseValue]]:undefined
  //__proto__:Promise {constructor: , then: , catch: , …}
//2秒後,我運行了
//異步操作成功後執行我: 異步操作成功了
//5秒後,我執行了
//Promise { resolved }
  //[[PromiseStatus]]:"resolved"
  //[[PromiseValue]]:"異步操作成功了"
  //__proto__:Promise {constructor: , then: , catch: , …}

由執行結果可知,變量promise仍然是new Promise對象的一個實例。所以return語句雖然被執行了,但對promise實例不會產生任何影響,相當於不存在。

由上面測試的代碼可知,Promise對象有以下兩個特點。
  (1)對象的狀態不受外界影響。Promise對象代表一個異步操作,有三種狀態:Pending(進行中)、Resolved(已完成,又稱Fulfilled) 和Rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,

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

resolve(value) VS resolve(Promise)

我們會在異步操作成功時調用resolve函數,其作用是將Promise對象的狀態從Pending變為Resolved,並將異步操作的結果作為參數傳遞給then()方法裏的第一個函數的形參

那麽傳入的參數是值和傳入的參數是promise對象時有什麽不同呢。我們來看一下例子。

當傳入的參數為值時:

var time = new Date();
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("1秒後,我運行了");
        resolve(‘異步操作成功了‘);     //1
    }, 2000) 
    
}).then(function (value) {
    console.log(value,new Date() - time);
})
//執行的輸出結果為:
//2秒後,我運行了
//異步操作成功了 1002

大約過了一秒左右,我們可以看到在resolved狀態的回調方法中,我們打印出了上面註釋中的內容。我們能夠通過resolve方法傳遞操作的結果,然後在回調方法中使用這些結果。

如果我們在resolve中傳入一個Promise實例呢?

var time = new Date();
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒後,我運行了");
        resolve(‘異步操作成功了‘);     //1
    }, 2000) 
    
})

var promise2 = new Promise(function (resolve,reject) {
    setTimeout(resolve,1000,promise);
}).then(function (value) {
    console.log(value,new Date() - time);
})

//執行後輸出的結果為:
//
2秒後,我運行了 //異步操作成功了 2003

promise2經過了2秒後才打印出來結果。奇怪了,我們不是設置promise2經過1秒後執行嗎?

簡單說就是因為promise2中的resolve()函數傳入了promise對象,此時promise對象的狀態決定了promise的狀態,同時會把返回值傳給promise。

Promise/A+中規定 [[Resolve]](promise, x)

2.3.2.如果x是一個promise實例, 則以x的狀態作為promise的狀態

  2.3.2.1.如果x的狀態為pending, 那麽promise的狀態也為pending, 直到x的狀態變化而變化。

  2.3.2.2.如果x的狀態為fulfilled, promise的狀態也為fulfilled, 並且以x的不可變值作為promise的不可變值。

  2.3.2.3.如果x的狀態為rejected, promise的狀態也為rejected, 並且以x的不可變原因作為promise的不可變原因。

2.3.4.如果x不是對象或函數,則將promise狀態轉換為fulfilled並且以x作為promise的不可變值。

Promise.prototype.then()

Promise實例具有then方法,也就是說,then方法是定義在原型對象Promise.prototype上的。它的作用是為Promise實例添加狀態改變時的回調函數。 前面說過,then方法的第一個參數是Resolved狀態的回調函數,第二個參數(可選)是Rejected狀態的回調函數。

then方法返回的是一個新的Promise實例(註意,不是原來那個Promise實例)。因此可以采用鏈式寫法,即then方法後面再調用另一個then方法。

var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒後,我運行了");
        resolve(‘異步操作成功了‘);     //1
    }, 2000) 
    
})
promise.name = ‘promise‘;
console.log(‘promise:‘,promise)
var promise2 = promise.then(function (value) {
    console.log(value);
})
promise2.name = ‘promise2‘;
console.log(‘promise2:‘,promise2);

// promise:
//     Promise { pending }
//     [[PromiseStatus]]:"pending"
//     [[PromiseValue]]:undefined
//     name:"promise"
//     __proto__:Promise {constructor: , then: , catch: , …}
// promise2:
// Promise { pending }
//     [[PromiseStatus]]:"pending"
//     [[PromiseValue]]:undefined
//     name:"promise2"
//     __proto__:Promise {constructor: , then: , catch: , …}

我們可以知道promise.then()方法執行後返回的是一個新的Promise對象。也就是說上面代碼中的promise2是一個Promise對象,它的實現效果和下面的代碼是一樣的,只不過在then()方法裏,JS引擎已經自動幫我們做了。

promise2 = new Promise(function (resolve,reject) {})

既然在then()函數裏已經自動幫我實現了一個promise對象,但是我要怎麽才能給resolve()或reject()函數傳參呢?其實在then()函數裏,我們可以用return()的方式來給promise2的resolve()或reject()傳參。看一個例子。

//var time = new Date();
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒後,我運行了");
        resolve(‘異步操作成功了‘);     //1
        //reject(‘異步操作失敗了‘);    //2
    }, 2000) 
    
})
//console.log(‘promise:‘,promise)
var promise2 = promise.then(function (value) {
    console.log(value);
    //resolve(‘nihao‘);  //報錯。註意,這裏不能寫resolve()方法,因為在then函數裏是沒有resolve方法的
    return (1);       
    //return promise;   //也可以return一個promise對象,返回promise對象執行後resolve(‘參數值‘)或reject(‘參數值‘)內部的參數值
  //如果不寫return的話,默認返回的是return undefined 。
}) var promise3 = promise2.then(function (value) { console.log(‘is:‘,value); },function (value) { console.log(‘error:‘,value); }) promise2.name = ‘promise2‘; setTimeout(function () { console.log(promise2); },3000); //2秒後,我運行了 //異步操作成功了 //is: 1 //Promise {resolved} //name:"promise2" //__proto__:Promise //[[PromiseStatus]]:"resolved" //[[PromiseValue]]:1

Promise與錯誤狀態處理

.then(null, rejection),用於指定異步操作發生錯誤時執行的回調函數。下面我們做一個示例。

var promise = new Promise(function(resolve, reject) {
    setTimeout(function () {
            reject(‘error‘);
    },2000);
}).then(null,function(error) {
    console.log(‘rejected‘, error)
});
//rejected error

我們知道then()方法執行後返回的也是一個promise對象,因此也可以調用then()方法,但這樣的話為了捕獲異常信息,我們就需要為每一個then()方法綁定一個.then(null, rejection)。由於Promise對象的錯誤信息具有“冒泡”性質,錯誤會一直向後傳遞,直到被捕獲為止。因此Promise為我們提供了一個原型上的函數Promise.prototype.catch()來讓我們更方便的 捕獲到異常。

我們看一個例子

 

var promise = new Promise(function(resolve, reject) {
    setTimeout(function () {
            reject(‘error‘);
    },2000);
}).then(function(value) {
    console.log(‘resolve‘, value);
}).catch(function (error) {
    console.log(error);
})
//運行結果
//error

上面代碼中,一共有二個Promise對象:一個由promise產生,一個由then產生。它們之中任何一個拋出的錯誤,都會被最後一個catch捕獲。

但是如果用.then(null, rejection)方法來處理錯誤信息,我們需要在每一個rejection()方法中返回上一次異常信息的狀態,這樣當調用的then()方法一多的時候,對會對代碼的清晰性和邏輯性造成影響。

所以,一般來說,不要在then方法裏面定義Reject狀態的回調函數(即then的第二個參數),總是使用catch方法。

入門Promise的正確姿勢