探祕Promise

@探祕Promisejavascript

create by db on 2019-8-18 18:54:39
Recently revised in 2019-9-2 21:51:02前端

Hello 小夥伴們,若是以爲本文還不錯,麻煩點個贊或者給個 star,大家的贊和 star 是我前進的動力!GitHub 地址java

 查閱網上諸多資料,並結合本身的學習經驗,寫下這篇學習筆記,以記錄本身的學習心得。現分享給你們,以供參考。node

前言

I hear and I fogorget.jquery

I see and I remember.git

I do and I understand.github

 咱們都知道,js的世界是單線程執行的,也就是說一個任務完成以後才能進行另外一個任務,這是由於js是運行在宿主進程多腳本語言,好比瀏覽器,好比node,宿主進程只會爲其分配一個js引擎線程。ajax

 那麼對於耗時比較長的操做,例如一些ajax異步請求,這些請求之間有一種關係,就是下一次請求的參數是上一次請求的結果——回調地獄。所幸ES6給咱們提供了異步編程的一種解決方案——Promise。編程

 參考文獻json

正文

什麼是Promise

Promise的含義

 Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。

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

Promise對象的特色

Promise的優勢

  1. 對象的狀態不受外界影響。promise對象表明一個異步操做,有三種狀態,pending(進行中)、fulfilled(已成功)、rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態,這也是promise這個名字的由來——「承諾」;

  2. 一旦狀態改變就不會再變,任什麼時候候均可以獲得這個結果。promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled,從pending變爲rejected。這時就稱爲resolved(已定型)。若是改變已經發生了,你再對promise對象添加回調函數,也會當即獲得這個結果。這與事件(event)徹底不一樣,事件的特色是:若是你錯過了它,再去監聽是得不到結果的。

 有了Promise對象,就能夠將異步操做以同步操做的流程表達出來,可使用一種鏈式調用的方式來組織代碼,避免了層層嵌套的回調函數。讓代碼更加的直觀。此外,Promise對象提供統一的接口,使得控制異步操做更加容易。

Promise的缺點

  1. 沒法取消Promise,一旦新建它就會當即執行,沒法中途取消。
  2. 若是不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
  3. 當處於pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。

那咱們爲何要使用promise?

好比咱們在工做中常常會碰到這麼一個需求,好比我使用ajax發一個A請求後,成功後拿到數據,咱們須要把數據傳給B請求;那麼咱們須要以下編寫代碼:

$.ajax({
    url: '',
    dataType:'json',
    success: function(data) { // 獲取data數據 傳給下一個請求
      var id = data.id;
      $.ajax({
        url:'',
        data:{"id":id},
        success:function(){ // .....
 }
      });
    }
});
複製代碼

 如上代碼;上面的代碼有以下幾點缺點:

  1. 後一個請求須要依賴於前一個請求成功後,將數據往下傳遞,會致使多個ajax請求嵌套的狀況,代碼不夠直觀。
  2. 若是先後兩個請求不須要傳遞參數的狀況下,那麼後一個請求也須要前一個請求成功後再執行下一步操做,這種狀況下,那麼也須要如上編寫代碼,致使代碼不夠直觀。

如何建立promise對象?

 要想建立promise對象,可使用new來調用promise的構造器來進行實例化。

以下代碼:

var promise = new Promise(function(resolve,reject){
  // ... some code
  if(/*異步操做成功*/) {
  resolve(value) // 成功調用resolve 往下傳遞參數 且只接受一個參數
  }else {
  reject(error)  // 失敗調用reject 往下傳遞參數 且只接受一個參數
  }   
});
複製代碼

 對經過new 生成的promise對象爲了設置其值在resolve(成功) / reject(失敗) 時調用的回調函數,可使用promise.then()實例方法。

以下代碼:

promise.then(onFulfilled, onRejected);
複製代碼

resolve(成功) 時 調用onFulfilled 方法,reject(失敗) 時 調用onRejected方法;

Promise.then 成功和失敗時均可以使用,若是出現異常的狀況下能夠採用promise.then(undefined,onRejected) 這種方式,只指定onRejected回調函數便可,不過針對這種狀況下咱們有更好的選擇是使用catch這個方法;代碼以下:

promise.catch(onRejected);
複製代碼

 上面囉嗦了這麼多,咱們來分別來學習相關的promise對象中的方法知識點吧!

Promise.resolve

 通常狀況下咱們都會使用new Promise()來建立promise對象,可是咱們也可使用promise.resolvepromise.reject這兩個方法;

Promise.resolve(value)的返回值也是一個promise對象,咱們能夠對返回值進行.then調用;

以下代碼:

Promise.resolve(11).then(function(value){
  console.log(value); // 打印出11
});
複製代碼

resolve(11)代碼中,會讓promise對象進入肯定(resolve狀態),並將參數11傳遞給後面的then所指定的onFulfilled 函數;

 咱們上面說過建立promise對象,可使用new Promise的形式建立對象,可是咱們這邊也可使用Promise.resolve(value)的形式建立promise對象;

Promise.reject

Promise.reject 也是new Promise的快捷形式,也建立一個promise對象。

好比以下代碼:

Promise.reject(new Error(「我錯了,請原諒俺!!」));
複製代碼

就是下面的代碼new Promise的簡單形式:

new Promise(function(resolve,reject){
   reject(new Error("我錯了,請原諒俺!!"));
});
複製代碼

 下面咱們來綜合看看使用resolve方法和reject方法。

demo以下:

function testPromise(ready) {
  return new Promise(function(resolve,reject){
    if(ready) {
      resolve("hello world");
    }else {
      reject("No thanks");
    }
  });
};
// 方法調用
testPromise(true).then(function(msg){
  console.log(msg);
},function(error){
  console.log(error);
});
複製代碼

 上面的代碼的含義是給testPromise方法傳遞一個參數,返回一個promise對象,若是爲true的話,那麼調用promise對象中的resolve()方法,而且把其中的參數傳遞給後面的then第一個函數內,所以打印出 「hello world」, 若是爲false的話,會調用promise對象中的reject()方法,則會進入then的第二個函數內,會打印No thanks

理解Promise異步調用的操做

var promise = new Promise(function(resolve){
  console.log(1);
  resolve(3);
});
promise.then(function(value){
  console.log(value);
});
console.log(2);
複製代碼

 上面的代碼輸出咱們能夠看到,結果分別爲1,2,3

 首先代碼從上往下執行,首先輸出1,而後調用resolve(3)這個方法,這時候promise對象變爲肯定狀態,即調用onFulFilled這個方法,從上面瞭解到,resolve(成功) 時 調用onFulfilled 方法,Promise.then 成功和失敗時均可以使用,所以第一個函數是成功調用的,可是Promise對象是以異步方式調用的,因此先執行console.log(2),輸出的是2,而後輸出的是3

理解是同步調用仍是異步調用

function ready(fn){
  var readyState = document.readyState;
  if (readyState === 'interactive' || readyState === 'complete') {
    fn();
  } else {
    window.addEventListener('DOMContentLoaded', fn);
  }
}
ready(function(){
  console.log("DOM Load Success");
});
console.log("我是同步輸出的");
複製代碼

 如上代碼;若是在調用ready()方法以前DOM已經載入完成的話,就會對回調函數進行同步調用,先輸出DOM Load Success後輸出 我是同步輸出的 ;若是在調用ready()方法以前DOM爲未載入完成的話,那麼代碼先會執行 window.addEventListener(‘DOMContentLoaded’, fn); 就會異步調用該函數,那麼就會先輸出 「我是同步輸出的」,後輸出「DOM Load Success」; 爲了解決上面的同步或者異步混亂的問題,咱們如今可使用promise對象使用異步的方式來解決;

以下代碼:

function readyPromise(){
  return new Promise(function(resolve,reject){
    var readyState = document.readyState;
    if (readyState === 'interactive' || readyState === 'complete') {
      resolve();
    } else {
      window.addEventListener('DOMContentLoaded', resolve);
    }
  });
}
readyPromise().then(function(){
  console.log("DOM Load Success");
});
console.log("我是同步加載的,先執行我");
複製代碼

 輸出以下:先輸出「我是同步加載的,先執行我」 後輸出 「DOM Load Success」。由於promise對象是異步加載的。

理解promise的三種狀態

Promise 對象有三種狀態:

  • Resolve能夠理解爲成功的狀態;
  • Rejected 能夠理解爲失敗的狀態;
  • Pending既不是Resolve也不是Rejected狀態;能夠理解爲Promise對象實例建立時候的初始狀態;

 好比Promise對象中的resolve方法就是調用then對象的第一個函數,也就是成功的狀態;而reject方法就是調用then對象的第二個函數,也就是失敗的狀態;

理解then()

 仍是以前的例子:

function testPromise(ready) {
  return new Promise(function(resolve,reject){
    if(ready) {
      resolve("hello world");
    }else {
      reject("No thanks");
    }
  });
};
// 方法調用
testPromise(true).then(function(msg){
  console.log(msg);
},function(error){
  console.log(error);
});
複製代碼

 上面的代碼就是利用了 then(onFulfilled,onRejected)方法來執行的,第一個方法就是成功狀態的標誌,第二個方法是失敗的狀態標誌;

 固然在多個任務的狀況下then方法一樣可使用;好比上面的代碼改爲以下:

function testPromise(ready) {
  return new Promise(function(resolve,reject){
    if(ready) {
      resolve("hello world");
    }else {
      reject("No thanks");
    }
  });
};
// 方法調用
testPromise(true).then(function(msg){
  console.log(msg);
}).then(testPromise2)
  .then(testPromise3);
function testPromise2(){
  console.log(2);
}
function testPromise3(){
  console.log(3);
}
複製代碼

輸出以下:hello world ,2,3

 上面的代碼是then的鏈式調用方式,輸出是按順序輸出的 分別爲 hello world , 2,3; 使用鏈式調用的緣由是每次調用後都會返回promise對象;

理解Promise.catch()方法

Promise.catch()方法是promise.then(undefined,onRejected)方法的一個別名,該方法用來註冊當promise對象狀態變爲Rejected的回調函數。

以下代碼:

var promise = Promise.reject(new Error("message"));
promise.catch(function(error) {
  console.log(error);
});
複製代碼

 無論是then仍是catch方法調用,都返回一個新的promise對象;

下面咱們來看看這個例子:

var promise1 = new Promise(function(resolve){
  resolve(1);
});
var thenPromise = promise1.then(function(value){
  console.log(value);
});
var catchPromise = thenPromise.catch(function(error){
  console.log(error);
});
console.log(promise1 !== thenPromise); // true
console.log(thenPromise !== catchPromise); //true
複製代碼

 如上代碼,打印的都是true,這說明無論是then仍是catch都返回了和新建立的promise是不一樣的對象;

 若是咱們知道了then方法每次都會建立返回一個新的promise對象的話,那麼久不難理解下面的代碼了;

以下:

var promise1 = new Promise(function(resolve){
  resolve(1);
});
promise1.then(function(value){
  return value * 2;
});
promise1.then(function(value){
  return value * 2;
});
promise1.then(function(value){
  console.log("1"+value);
});
複製代碼

 如上的代碼;打印出11;由於他們每次調用then方法時,是使用的不一樣的promise對象;所以最後打印的value仍是1;可是若是咱們then方法是連續調用的話,那狀況就不同了。

好比以下代碼:

var promise1 = new Promise(function(resolve){
  resolve(2);
});
promise1.then(function(value){
  return value * 2;
}).then(function(value){
  return value * 2;
}).then(function(value){
  console.log("1"+value);
});
複製代碼

 打印出18,即 「1」 + 2 * 2 * 2 = 18;

 上面第一種方法沒有使用方法鏈的調用,上面第一種那種寫法then 調用幾乎是同時開始進行的,且傳給每一個then的value都是1

 第二種方式是使用方法鏈的then,使多個then方法鏈接在一塊兒了,所以函數會嚴格執行 resolve – then — then – then的順序執行,而且傳遞每一個then方法的value的值都是前一個promise對象中return的值;所以最後的結果就是18了;

 如今咱們再回過頭一剛開始咱們討論的爲何要使用promise的緣由的問題了,好比2個ajax請求,後一個ajax請求須要獲取到前一個ajax請求的數據,咱們以前在使用jquery寫代碼是以下的:

$.ajax({
   url: '',
   dataType:'json',
   success: function(data) {
  // 獲取data數據 傳給下一個請求
  var id = data.id;
  $.ajax({
    url:'',
    data:{"id":id},
    success:function(){
      // .....
    }
  });
  }
});
複製代碼

 如今咱們學習了then方法後,咱們能夠從新編寫上面的代碼變成以下:

var ajaxPromise = new Promise(function(resolve){
  resolve();
});
ajaxPromise.then(function(){
  $.ajax({
    url:'',
    dataType:'json',
    success: function(data) {
      var id = data.id;
      return id;
    }
  })
}).then(function(id){
  $.ajax({
    url:'',
    dataType:'json',
    data:{"id":id},
    success: function(data){
      console.log(data);
    }
  })
});
複製代碼

理解Promise.all

Promise.all方法用於將多個Promise實例包裝成一個新的Promise實例。

 Promise.all能夠接受一個元素爲Promise對象的數組做爲參數,當這個數組裏面全部的promise對象都變爲resolve時,該方法纔會返回。

以下代碼:

var promise1 = new Promise(function(resolve){
  setTimeout(function(){
    resolve(1);
  },3000);
});
var promise2 = new Promise(function(resolve){
  setTimeout(function(){
    resolve(2);
  },1000);
});
Promise.all([promise1,promise2]).then(function(value){
  console.log(value); // 打印[1,2]
});
複製代碼

 如上代碼 打印的是[1,2]; 如上咱們看到promise1對象中的setTimeout是3秒的時間,而promise2對象中的setTimeout是1秒的時間,可是在Promise.all方法中會按照數組的原先順序將結果返回;

 在咱們平時的需求中,或許有這種狀況的需求,好比咱們須要發2個ajax請求時,無論他們的前後順序,當這2個ajax請求都同時成功後,咱們須要執行某些操做的狀況下,這種狀況很是適合。

理解Promise.race

 如上可知:Promise.all在接收到的全部對象promise都變爲FulFilled或者 Rejected狀態以後纔會繼續後面的處理,可是Promise.race的含義是隻要有一個promise對象進入FulFilled或者Rejected狀態的話,程序就會中止,且會繼續後面的處理邏輯;

以下代碼:

// `delay`毫秒後執行resolve
function timerPromise(delay){
  return new Promise(function(resolve){
    setTimeout(function(){
      resolve(delay);
    },delay);
  });
}
// 任何一個promise變爲resolve或reject 的話程序就中止運行
Promise.race([
  timerPromise(1),
  timerPromise(32),
  timerPromise(64),
  timerPromise(128)
]).then(function (value) {
  console.log(value);  // => 1
});
複製代碼

 如上代碼建立了4個promise對象,這些promise對象分別在1ms,32ms,64ms,128ms後變爲肯定狀態,而且在第一個變爲肯定狀態後1ms後,then函數就會被調用,這時候resolve()方法給傳遞的值爲1,所以執行then的回調函數後,值變爲1

 咱們再來看看當一個promise對象變爲肯定狀態(FulFiled)的時候,他們後面的promise對象是否還在運行呢?咱們繼續看以下代碼運行:

var runPromise = new Promise(function(resolve){
  setTimeout(function(){
    console.log(1);
    resolve(2);
  },500);
});
var runPromise2 = new Promise(function(resolve){
  setTimeout(function(){
    console.log(3);
    resolve(4);
  },1000);
});

// 第一個promise變爲resolve後程序中止
Promise.race([runPromise,runPromise2]).then(function(value){
  console.log(value);
});
複製代碼

 如上代碼是使用定時器調用的,上面是2個promise對象,咱們看到第一個promise對象過500毫秒後加入到執行隊列裏面去,若是執行隊列沒有其餘線程在運行的時候,就執行該定時器,因此第一次打印1,而後調用resolve(2); 接着調用promise.race方法,該方法只要有一個變爲成功狀態(FulFiled)的時候,程序就會中止,所以打印出2,同時後面的promise對象接着執行,所以打印出3,可是因爲promise.race()該方法已經中止調用了,因此resolve(4)不會有任何輸出;所以最後輸出的是1,2,3

 由此咱們得出結論:當一個promise對象變爲(FulFilled)成功狀態的時候,後面的promise對象並無中止運行。

總結

 做爲javascript的新標準,ES6爲咱們提供了不少新語法及新特性,讓咱們的代碼更加簡單與易用。

 做爲一隻前端菜鳥,本篇文章旨在記錄本身的學習心得,若有不足,還請多多指教,謝謝你們。

 路漫漫其修遠兮,與諸君共勉。

後記:Hello 小夥伴們,若是以爲本文還不錯,記得點個贊或者給個 star,大家的贊和 star 是我編寫更多更豐富文章的動力!GitHub 地址

知識共享許可協議
db 的文檔庫http://www.javashuo.com/tag/db 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議進行許可。
基於github.com/danygitgit上的做品創做。
本許可協議受權以外的使用權限能夠從 creativecommons.org/licenses/by… 處得到。

相關文章
相關標籤/搜索