什麼是promise

你們好,我是IT修真院武漢分院第14期學員,一枚正直善良的web程序員。javascript

今天給你們分享一下,修真院官網JS-9任務中什麼是promisehtml

1、背景介紹
什麼是promise?java

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

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

2、知識剖析
一、Promise的基本用法es6

ES6 規定,Promise對象是一個構造函數,用來生成Promise實例。下面代碼創造了一個Promise實例。github

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

二、resolve和rejectweb

Promise構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolve和reject。編程

它們是兩個函數,由JavaScript引擎提供,不用本身部署。數組

resolve函數的做用是,將Promise對象的狀態從「未完成」變爲「成功」(即從Pending變爲

Resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;reject函數的做用是,

將Promise對象的狀態從「未完成」變爲「失敗」(即從Pending變爲

Rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。

Promise實例生成之後,能夠用then方法分別指定Resolved狀態和Rejected狀態的回調函數。

p.then(function (value) {
  // success
}, function (error) {
  // failure
});

3、常見問題
如何使用Promise?

4、解決方案
一、用法以下:

須要注意的是,new一個Promise對象時,裏面的代碼就直接運行了。

因此咱們用Promise的時候通常是包在一個函數中,在須要的時候去運行這個函數。

function runAsync(){
  var p = new Promise(function(resolve, reject){
      //作一些異步操做
      setTimeout(function(){
          console.log('執行完成');
          resolve('隨便什麼數據');
      }, 2000);
  });
  return p;
}
runAsync().then(function(data){
  console.log(data);
  //後面能夠用傳過來的數據作些其餘操做
  //......
});

若是不用Promise而是用回調函數去寫:

then裏面的函數就跟咱們平時的回調函數一個意思,可以在runAsync這個 異步任務執行完成以後被執行。這就是Promise的做用了,簡單來說,就是能把 原來的回調寫法分離出來,在異步操做執行完後,用鏈式調用的方式執行回調函數。 寫成回調函數以下:

//回調函數寫法
function runAsync(callback) {
  setTimeout(function () {
      console.log('執行完成');
      callback('隨便什麼數據');
  }, 2000);
};
runAsync(function (data) {
  console.log(data);
});

二、關於事件循環和任務隊列

講事件循環和任務隊列以前,先講一下我發現的setTimeout機制

//下面看一下setTime函數機制 例1
setTimeout(function () {//給延時函數設置爲0,但願它當即運行
  console.log(1);
}, 0);

console.log(2);

你以爲先輸出哪一個。
看上去好像是先輸出1,再輸出2.實際上卻不是這樣的。其實是先1,再2.爲何呢?

由於JS單線程的事件循環和任務隊列。

下面給你們科普一下關於任務隊列。 
(1)全部同步任務都在主線程上執行,造成一個執行棧。 
(2)主線程以外,還存在一個"任務隊列"。任務隊列又分爲macrotask和microtask。任務隊列能夠有多個。 
(3)在主線程執行棧執行空以後,會先讀取全部的微觀隊列,而後再讀取一個宏觀隊列。再讀取全部的微觀隊列。
macrotasks: script(總體代碼),setTimeout, setInterval,Ajax
microtasks: Promise

clipboard.png

瞭解他們的關係以後,咱們再來看一個例子

console.log(1);

setTimeout(function () {
  console.log(2);//setTimeout在宏觀任務隊列
}, 0);

new Promise(function (resolve) {
  console.log(3);//異步第一部分是屬於執行棧
  resolve();
  console.log(4);
}).then(function () {
  console.log(5);//回調的函數在微觀任務隊列
});

console.log(6);

想想輸出數字1-6的輸出順序是怎麼樣的?若是能答上來,說明你大體仍是理解了上面介紹的事件循環和任務隊列。
答案請先本身思考下,否則我是不會告訴你答案在文章最後面的。

三、接下來繼續講promise和回調函數兩種寫法的區別
那麼問題來了,既然回調函數回調函數能實現異步操做,爲何還要用Promise呢?
請考慮這種狀況:有多層回調該怎麼辦?若是callback也是一個異步操做,並且執行 完後也須要有相應的回調函數,該怎麼辦呢?總不能再定義一個callback2,而後給 callback傳進去吧。而Promise的優點在於,能夠在then方法中繼續寫Promise對 象並返回,而後繼續調用then來進行回調操做。

四、PROMISE的鏈式調用

因此,從表面上看,Promise只是可以簡化層層回調的寫法,而實質上,Promise的精髓是「狀態」, 用維護狀態、傳遞狀態的方式來使得回調函數可以及時調用,它比傳遞callback函數要簡單、靈活的多。 因此使用Promise的正確場景是這樣的:

runAsync1()
  .then(function (data) {
      for (i = 0; i < 1000000; i++) {
          i = i - 0.5;
      }
      console.log(data, new Date() - start, "ms"); //當前時間減開始時間
      return runAsync2();
  })
  .then(function (data) {
      for (i = 0; i < 1000000; i++) {
          i = i - 0.5;
      }
      console.log(data, new Date() - start, "ms"); //當前時間減開始時間
      return runAsync3();
      // return '直接返回數據';

  })
  .then(function (data) {
      for (i = 0; i < 1000000; i++) {
          i = i - 0.5;
      }
      console.log(data, new Date() - start, "ms"); //當前時間減開始時間
  });

runAsync一、runAsync二、runAsync3這三個函數的定義:

var start = new Date();
function runAsync1() {
  var p = new Promise(function (resolve, reject) {
      for (i = 0; i < 1000000; i++) {
          i = i - 0.5;
      }
      console.log('異步任務1執行完成:', new Date() - start, "ms");
      resolve('回調函數1');
  });
  return p;
}

function runAsync2() {
  var p = new Promise(function (resolve, reject) {
      for (i = 0; i < 1000000; i++) {
          i = i - 0.5;
      }
      console.log('異步任務2執行完成:', new Date() - start, "ms");
      resolve('回調函數2');

  });
  return p;
}

function runAsync3() {
  var p = new Promise(function (resolve, reject) {
      for (i = 0; i < 1000000; i++) {
          i = i - 0.5;
      }
      console.log('異步任務3執行完成:', new Date() - start, "ms");
      resolve('回調函數3');

  });
  return p;
}

結果爲:

for循環運行事件大體在130ms左右。因此每一步都大概這麼多,這就是promise的鏈式。

在then方法中,你也能夠直接return數據而不是Promise對象,在後面的then中就能夠接收到數據了,好比咱們把上面的代碼修改爲這樣:

 

runAsync1()
  .then(function (data) {
      for (i = 0; i < 1000000; i++) {
          i = i - 0.5;
      }
      console.log(data, new Date() - start, "ms"); //當前時間減開始時間
      return runAsync2();
  })
  .then(function (data) {
      for (i = 0; i < 1000000; i++) {
          i = i - 0.5;
      }
      console.log(data, new Date() - start, "ms"); //當前時間減開始時間   
      return '直接返回數據';

  })
  .then(function (data) {
      for (i = 0; i < 1000000; i++) {
          i = i - 0.5;
      }
      console.log(data, new Date() - start, "ms"); //當前時間減開始時間
  });

這樣輸出的結果是:

clipboard.png

能夠看到異步任務3沒有執行了,由於返回的是數據。可是後面的then仍是會執行,打印出"直接返回數據"

五、REJECT的用法

事實上,咱們前面的例子都是隻有「執行成功」的回調,尚未「失敗」的狀況, reject的做用就是把Promise的狀態置爲rejected,這樣咱們在then中就 能捕捉到,而後執行「失敗」狀況的回調。看下面的代碼。

//reject用法
function getNumber() {
  var p = new Promise(function (resolve, reject) {
      //作一些異步操做
      setTimeout(function () {
          var num = Math.ceil(Math.random() * 10); //生成1-10的隨機數
          if (num <= 5) {
              reject('數字過小了');
          } else {
              resolve(num);
          }
      }, 1000);
  });
  return p;
}
getNumber()
  .then(
      function (data) {
          console.log('resolved');
          console.log(data);
      },
      function (reason, data) {
          console.log('rejected');
          console.log(reason);
      }
  );

七、CATCH的用法

咱們知道Promise對象除了then方法,還有一個catch方法,它是作什麼用的呢? 其實它和then的第二個參數同樣,用來指定reject的回調,用法是這樣:

//catch回調失敗
getNumber()
    .then(function (data) {
        console.log('resolved');
        console.log(data);
    })
    .catch(function (reason) {
        console.log('rejected');
        console.log(reason);
    });

結果:

clipboard.png

clipboard.png

上面代碼的效果和寫在then的第二個參數裏面同樣。不過它還有另一個做用:在執行resolve的回調 (也就是上面then中的第一個參數)時,若是拋出異常了(代碼出錯了),那麼並不會報錯 卡死js,而是會進到這個catch方法中。請看下面的代碼:

getNumber()
    .then(function (data) {
        console.log('resolved');
        console.log(data);
        console.log(somedata); //此處的somedata未定義
    })
    .catch(function (reason) {
        console.log('reject');
        console.log(reason);
    });

也就是說進到catch方法裏面去了,並且把錯誤緣由傳到了reason參數中。 即使是有錯誤的代碼也不會報錯了,這與咱們的try/catch語句有相同的功能。

結果爲:

clipboard.png

能夠看到每次運行都是catch結果,並且未報錯,是由於直接進入catch。打印catch裏的內容。

5、編碼實戰
見視頻或者ppt

6、拓展思考
一、all的用法

Promise的all方法提供了並行執行異步操做的能力,而且在全部異步操做執行完後才執行回調。 咱們仍舊使用上面定義好的runAsync一、runAsync二、runAsync3這三個函數,看下面的例子:

//拓展思考之all
Promise
    .all([runAsync1(), runAsync2(), runAsync3()])
    .then(function (results) {
        console.log(results);
    });

用Promise.all來執行,all接收一個數組參數,裏面的值最終都 算返回Promise對象。這樣,三個異步操做的並行執行的,等到它 們都執行完後纔會進到then裏面。那麼,三個異步操做返回的數據哪裏 去了呢?都在then裏面呢,all會把全部異步操做的結果放進一個數組中 傳給then,就是上面的results

有了all,你就能夠並行執行多個異步操做,而且在一個回調中處理全部的返回數 據,是否是很酷?有一個場景是很適合用這個的,一些遊戲類的素材比較多的應用, 打開網頁時,預先加載須要用到的各類資源如圖片、flash以及答案134652各類靜態文件。所 有的都加載完後,咱們再進行頁面的初始化。

二、race的用法

all方法的效果其實是「誰跑的慢,以誰爲準執行回調」, 那麼相對的就有另外一個方法「誰跑的快,以誰爲準執行回調」, 這就是race方法,這個詞原本就是賽跑的意思。race的用法 與all同樣,看一下下面的代碼:

Promise
    .race([runAsync1(), runAsync2(), runAsync3()])
    .then(function (results) {
        console.log(results);
    });

在then裏面的回調開始執行時,runAsync2()和runAsync3()並無中止, 仍舊再執行。因而再過1秒後,輸出了他們結束的標誌。

這個race有什麼用呢?使用場景仍是不少的,好比咱們能夠用race給某個異步請求 設置超時時間,而且在超時後執行相應的操做,代碼以下:

 

//請求某個圖片資源
function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
            resolve(img);
        }
        img.src = './img/world.jpg';
    });
    return p;
}

//延時函數,用於給請求計時
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('圖片請求超時');
        }, 2);
    });
    return p;
}

Promise
.race([requestImg(), timeout()])
.then(function(results){
    console.log(results);
})
.catch(function(reason){
    console.log(reason);
});

requestImg函數會異步請求一張圖片,我把地址寫爲"xxxxxx",因此確定是無 法成功請求到的。timeout函數是一個延時2ms的異步操做。咱們把這兩個返回 Promise對象的函數放進race,因而他倆就會賽跑,若是2ms以內圖片請求成功了, 那麼遍進入then方法,執行正常的流程。若是2ms圖片還未成功返回,那麼timeout 就跑贏了,則進入catch,報出「圖片請求超時」的信息。

7、參考文獻
大白話講解Promise(一)

JavaScript Promise迷你書(中文版)

事件循環與任務隊列

也能夠看個人視頻,有較爲通俗易懂的講解:

https://v.qq.com/x/page/n0671...

8、更多討論
Q1:reject用法中,funtion1和funtion2可否對調位置?

A1:不行,沒有catch的話,默認第一個funtion是接受成功返回值,第二個接受失敗返回值。

Q2:callback的異步機制?

A2:callback會造成一個單獨的callback queue。一個宏觀隊列。

Q3:Ajax異步機制

A3:   請求是由瀏覽器新開一個線程請求(見前面的瀏覽器多線程)。當請求的狀態變動時,若是先前已設置回調,這異步線程就產生狀態變動 事件放到 JavaScript引擎的事件處理隊列中等待處理。當瀏覽器空閒的時候出隊列任務被處理,JavaScript引擎始終是單線程運行回調函數。 javascript引擎確實是單線程處理它的任務隊列,能理解成就是普通函數和回調函數構成的隊列

相關文章
相關標籤/搜索