簡單學習 Promise 對象

引言 Promise 是異步編程的一種解決方案,比傳統的解決方案——回調和事件——更合理且強大。最近的項目要用到這個,就參照阮一峯老師的《ES6標準入門》這本書簡單學一下了。javascript

1 Promise 的含義

所謂 Promise ,簡單來講就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上來看,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise 提供統一的 API,各類異步操做均可以用一樣的方法進行處理。java

Promise 對象有如下兩個特色。es6

  1. 對象的狀態不受外界影響。
  2. 一旦狀態改變就不會再變。

2 基本用法

ES6 規定,Promise 對象是一個構造函數,用來生成 Promise 實例。
舉個例子。ajax

var promise = new Promise(function (resolve, reject) {
  // some code
  if (/* 異步操做成功*/) {
     resolve(value);
  } else {
    // 異步操做失敗
    reject(error);
  }
});

Promise 構造函數接收一個函數做爲參數,該函數的兩個參數分別是 resolvereject。他們是兩個函數,由 Javascript 引擎提供,不用本身部署。編程

resolve 函數的做用是將 Promise 對象的狀態從『未完成』(Pending)變爲『成功』(Resolved),在異步操做成功的時候調用,並將異步操做的結果做爲參數傳遞過去。 reject 函數的做用是,將 Promise 對象的狀態從『未完成』(Pending)變爲『失敗』(Rejected)json

當咱們生成了一個 Promise 實例以後。就能夠用 then 方法分別指定 Resolved 狀態和 Rejected 狀態的回調函數。數組

promise.then(function (value) {
  // success
  console.log(value);
}, function (error) {
  // failed
  console.log(error);
});

then 方法能夠接受兩個回調函數做爲參數。第一個回調函數是 Promise 對象的狀態變爲 Resolved 時調用,第二個回調函數是 Promise 對象的狀態變爲 Rejected 時調用。其中第二個參數是可選的,不必定要提供。這兩個函數都接收 Promise 對象傳出的值做爲參數。promise

咱們來個小例子異步

let promise = new Promise(function(resolve,reject){
  console.log('Promise');
  let value = 'value';
  resolve(value);
});

promise.then(function(value){
  console.log(value);
});
console.log('Hi');

// Promise
// Hi
// value

上面的代碼中,Promise 新建後會當即執行,因此首先輸出的是 Promise。而後,then 方法指定的回調函數將當前腳本全部同步任務執行完成後纔會執行,因此 Resolved 最後輸出。異步編程

3 Promise.prototype.then()

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

then 方法返回的是一個新的 Promise 實例注意 不是原來的那個 Promise 實例)。所以能夠採用鏈式寫法,即 then 方法後面再調用另外一個 then 方法。

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
    // ...
});

上面的代碼使用 then 方法依次指定了兩個回調函數。第一個回調函數完成之後,將會返回結果做爲參數,傳入第二個回調函數。

而後採用鏈式的 then 能夠指定一組按順序調用的回調函數。這時,前一個回調函數有可能返回的仍是一個 Promise 對象(即有異步操做),然後一個回調函數就會等待該 Promise 對象的狀態發生變化,再被調用。

4 Promise.prototype.catch()

Promise.prototype.catch 方法是 .then(null, rejection) 的別名,用於指定發生錯誤時的回調函數。

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 處理 getJSON 和 前一個回調函數運行時發生的錯誤
  console.log('發生錯誤!', error);
});

上面的代碼中,getJSON 方法返回一個 Promise 對象,若是該對象狀態變爲 Resolved,則會調用 then 方法指定的回調函數;若是異步操做拋出錯誤,狀態就會變成 Rejected,而後調用 catch 方法指定的回調函數處理這個錯誤。另外, then 方法指定的回調函數若是在運行中拋出錯誤,也會被 catch 方法捕獲。

p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err));

  // 等同於
  p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("rejected:", err));

下面是一個例子。

const promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});
promise.catch(function(error) {
  console.log(error);
});
// Error: test

上面的代碼中,Promise 拋出一個錯誤就被 catch 方法指定的回調函數所捕獲。注意,上面的寫法和下面兩種寫法是等價的。

// 寫法一
const promise = new Promise(function(resolve, reject) {
  try {
    throw new Error('test');
  } catch(e) {
    reject(e);
  }
});
promise.catch(function(error) {
  console.log(error);
});

// 寫法二
const promise = new Promise(function(resolve, reject) {
  reject(new Error('test'));
});
promise.catch(function(error) {
  console.log(error);
});

由上面能夠看出, reject 方法的做用等同於拋出錯誤。
若是 Promise 狀態已經變成 Resolved,在拋出錯誤是無效的。

const promise = new Promise(function(resolve, reject) {
  resolve('ok');
  // Promise 狀態已變成 已完成
  throw new Error('test');
});
promise
  .then(function(value) { console.log(value) })
  .catch(function(error) { console.log(error) });
// ok

注意 通常來講,不要在 then 方法中定義 Reject 狀態的回調函數(即 then 的第二個參數),而是使用 catch 方法。

// bad
promise
  .then(function(data) {
  // success
  }, function(err) {
  // error
});

// good
promise
  .then(function(data) { //cb
  // success
  })
  .catch(function(err) {
  // error
  });

上面代碼中,第二種寫法要好於第一種寫法,理由是第二種寫法能夠捕獲前面 then 方法執行中的錯誤,也更接近同步的寫法(try/catch)。所以,建議老是使用 catch 方法,而不使用 then 方法的第二個參數。

5 Promise.all()

Promise.all 方法是將多個 Promise 對象實例包裝成一個新的實例。

var p = Promise.all([p1, p2, p3]);

上面的代碼中,Promise.all() 方法接受一個數組做爲參數,p1, p2, p3 都是 Promise 對象的實例。若是不是,就會先調用下面講到的 Promise.resolve 方法,將參數轉換爲 Promise 實例,再進一步處理(Promise.all 方法的參數不必定是數組,可是必須具備 Iterator 接口,且每一個返回成員都是 Promise 實例)。

p 的狀態由 p1, p2, p3 決定,分紅兩種狀況

  1. 只有 p1, p2, p3 的狀態都變成 FulFilled,p 的狀態纔會變成 FulFilled,此時 p1, p2, p3 的返回值組成一個數組,傳遞給 p 的回調函數。
  2. 只要 p1,p2,p3 中有一個被 Rejected ,p 的狀態就直接變成 Rejected,此時第一個被 Rejected 的實例的返回值會傳遞給 p 的回調函數。

下面是一個具體例子。

var promises = [2,3,4,5].map(function(id){
      console.log(id)
    });
    
    Promise.all(promises).then(function(res){
      console.log(res);
      resolve
    }).catch(function(error){
      console.log(error);
    });

// 先執行全部 promise 實例的異步操做,而後吧操做的結果打包數組返回
// 2 3 4 5 [undefined,undefined,undefined,undefined]

上面的代碼中,Promise 是包含 6 個 Promise 實例的數組,只有這 6 個實例的狀態都變成 fulfilled,或者其中有 1 個變成 rejected,纔會調用 Promise.all 方法後面的回調函數。

6 Promise.race()

Promise.race 方法一樣是將多個 Promise 實例包裝成一個新的 Promise 實例。

var p = Promise.race([p1, p2, p])

上面的代碼中,只要 p1, p2, p3 中 有一個實例 率先改變狀態,p 的狀態就跟着改變。那個率先改變的 Promise 實例的返回值就傳遞給 p 的回調函數。
Promise.race 方法的參數與 Promise.all 方法同樣,若是不是 Promise 實例,就會先調用下面講到的 Promise.resolve 方法,將參數轉爲 Promise 實例,再進一步處理。

下面是一個例子,若是指定時間內沒有得到結果,就將 Promise 的狀態變成 Rejected,不然就變爲 Resolved。

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

p
.then(console.log)
.catch(console.error);

上面代碼中,若是 5 秒以內 fetch 方法沒法返回結果,變量 p 的狀態就會變爲 rejected,從而觸發 catch 方法指定的回調函數。

7 Promise.resolve()

有時須要將現有對象轉爲 Promise 對象,Promise.resolve 方法就起到這個做用。

const jsPromise = Promise.resolve($.ajax('/whatever.json'));

上面代碼將 jQuery 生成的 deferred 對象,轉爲一個新的 Promise 對象。

Promise.resolve 等價於下面的寫法。

Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))

Promise.resolve方法的參數分紅四種狀況。

7.1 參數是一個 Promise 實例

若是參數是一個 Promise 實例,那麼 Promise.resolve 將不作任何修改,原封不動的返回這個實例。

7.2 參數是一個 thenable 對象

thenable 對象是指具備 then 方法的對象,例以下面這個對象

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

Promise.resolve 方法會將這個對象轉爲 Promise 對象,任何執行 thenable 對象的 then 方法。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});

上面的代碼中, thenable 對象的 then 方法執行後,對象 p1 的狀態就變爲 resolved,從而當即執行最後的 then 方法指定的回調函數。輸出 42。

7.3 參數不是具備 then 方法的對象,或根本就不是對象

若是參數是一個原始值,或者是一個不具備 then 方法的對象,那麼 Promise.resolved 方法返回一個新的 Promise 對象,狀態爲 Resolved。

const p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});
// Hello

上面代碼生成一個新的 Promise 對象的實例 p。因爲字符串 Hello 不屬於異步操做(判斷方法是字符串對象不具備 then 方法),返回 Promise 實例的狀態從一輩子成就是 resolved,因此回調函數會當即執行。Promise.resolve 方法的參數,會同時傳給回調函數。

7.4 不帶有任何參數

Promise.resolved 方法容許在調用時不帶有參數,而直接返回一個 Resolved 狀態的 Promise 對象。

因此,若是你但願獲得一個 Promise 對象,比較方便的方法就是直接調用 Promise.resolve 方法。

const p = Promise.resolve();

p.then(function () {
  // ...
});

上面代碼中的 p 就是一個 Promise 對象。
須要注意的是,當即 resolve 的 Promise 對象實在本輪 『事件循環』(event loop)結束時,而不是在下一輪『事件循環』開始時。

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

上面代碼中,setTimeout(fn, 0) 在下一輪『事件循環』開始時執行,Promise.resolve() 在本輪『事件循環』結束時執行,console.log('one') 則是當即執行,所以最早輸出。

8 Promise.reject()

Promise.reject(resson) 方法也會返回一個新的 Promise 實例,狀態爲 Rejected (這個就暫時想不懂怎麼應用了)。

const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))

p.then(null, function (s) {
  console.log(s)
});
// 出錯了

上面的代碼生成一個 Promise 對象的實例 p,狀態爲 Rejected,回調函數會當即執行。

總結

以上就是關於 Promise 學習的內容,若有錯誤的地方就請在下面評論處,發表一下見解,固然也能夠放一下關於進階學習 Promise 的文章,你們一塊兒學習。

相關文章
相關標籤/搜索