ES6 | 搞懂promise

【轉】原文地址:https://www.cnblogs.com/lvdabao/p/es6-promise-1.html
首先感謝原作者呂大豹大神的文章,用大白話講Promise,讓小白比較容易搞懂Promise,這里做個記錄,再貫通一下,也當是自己做筆記了。
先搞明白一個問題,什么是回調函數:

回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用為調用它所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應

回調函數就是把一個函數fn1()作為一個參數傳遞給另一個函數fn2(),當函數fn2()執行到某一步,或者滿足某種條件的時候才執行傳入的參數fun2()

1. 先看看promise是什么?

console.log('------Promise是什么:-----');

console.dir(Promise);

控制臺查看到如下的結果:

Promise

發現 Promise 是一個構造函數,自身有all()reject()resolve()這幾個眼熟的方法,prototype上有then()catch()等同樣很眼熟的方法。所以,用 Promise new 出來的對象,應該都有上述方法的:

// 實例化一個簡單的 Promise 對象
let p = new Promise(function (resolve, reject) {
            // 執行一個異步操作
            setTimeout(() => {
                console.log('異步操作執行完畢');
                resolve('resolve 回調函數的結果');
            }, 1000);
        });

Promise 對象接收一個函數做為參數,并且該函數參數又有兩個回調函數參數 resolverejectresolve 可以暫時理解為異步執行“成功”的回調函數, reject 可以暫時理解為異步執行“失敗”的回調函數;確切的說Promise 對象并沒有“成功”和“失敗”這一說,只是

  • resolve 回調函數將 Promise 狀態置為 fullfiled
  • reject 回調函數將 Promise 狀態置為 rejected

運行上面的代碼,會在1秒后輸出一個結果“異步操作執行完畢”;
注意:

  • 這里只是實例化了一個對象,并未調用它,但是傳入的匿名函數已經執行了,這點需要注意。所以,一般需要將Promise實例對象封裝一下,在需要的時候,去調用這個封裝的函數即可,對上例進行修改:
    function runAsync() {
      let p = new Promise(function (resolve, reject) {
      // 執行一個異步操作
      setTimeout(() => {
        console.log('異步操作執行完畢');
        resolve('resolve 回調函數的結果');
          }, 1000);
       });
       return p;
    }
    // 調用封裝的函數
    runAsync();// 異步操作執行完畢
    
  • resolve('resolve 回調函數的結果'); 這段代碼并沒有什么效果呢?
    這是因為上面說了 resolve 是一個回調函數,但是并沒有定義這個函數的邏輯操作 resolve('resolve 回調函數的結果'); 這段代碼只是給 resolve() 函數傳遞了一個“resolve 回調函數的結果”參數而已,但是 resolve() 具體邏輯操作是什么,并未定義;帶著這個問題繼續往下進行

2. Promise 能做什么?

在封裝的函數中,最后會 return 一個 Promise 實例,也就是說,執行這個函數我們得到了一個 Promise 實例對象。最開始,在控制臺輸出 Promise 結果可以看到有 thencatch 方法的,我們來用一下 then() 方法,then() 方法接受一個參數,這個參數也是函數,為這個函數參數傳入一個參數 params ,然后運行下面的代碼:

runAsync().then(function(params){
  console.log(params);
})

運行上面的代碼,會在1秒后輸出如下的結果:

then()

奇跡的發現,then() 傳入的函數參數不就是 Promise 構造的實例中傳入的那個 resolve 回調函數么,也就是說 resolve() 的執行邏輯是在 then()傳遞的函數參數中的,它在 runAsync() 這個異步任務執行完之后被執行了。這就是 Promise 的作用之一了:

  • 解構回調函數:把原始的回調函數寫法分離出來,寫在 then()中, 也就是解構了,在異步操作執行完成之后,用鏈式調用的方式執行回調函數

Promise 鏈式調用
從表面上看, Promise 只是能夠簡化層層回調的寫法,而實質上,Promise 的精髓是 “狀態”,用維護狀態、傳遞狀態的方式來使得回調函數能夠及時調用,它相比傳統的callback函數要簡單,靈活。如下面一個場景:

runAsync().then(function (params) {
  console.log(params);
  return runAsync1();
}).then(function (params) {
  console.log(params);
  return runAsync2();
}).then(function (param) {
  console.log(params);
});

這樣能夠按順序,每隔1秒輸出每個異步回調中的內容,在runAsync2中傳給resolve的數據,能在接下來的then方法中拿到。運行結果如下:


鏈式調用

runAsync2()runAsync3()的內部邏輯同 runAsync() 一致,就不再寫了

then() 方法中,也可以直接 return 數據,在后面的 then() 中就可以接收到數據了,比如把上面的代碼修改一下:

runAsync().then(function (params) {
  console.log(params);
  return runAsync1();
}).then(function (params) {
  console.log(params);
  return params;
}).then(function (param) {
  console.log(params);
});

運行后,輸出的結果是:


return 數據

reject 回調函數和 catch()方法的用法
上面了解了 resolve 回調函數和 then() 的用法,那么還有一個回調函數 reject 沒用呢,這個回調函數又是做什么用的呢?事實上,前面的例子都是只有“執行成功”的回調,但是沒有“執行失敗”的情況,前面說過 reject 回調函數將 Promise 狀態置為 rejected,這樣我們在then()就能捕捉到,然后執行“失敗”的情況的回調。下面做一個有“失敗”的情況:

function getNum() {
  // promise 對象接收一個函數參數,并且該函數參數擁有兩個函數參數,這兩個函數參數是回調函數
  let p = new Promise(function (resolve, reject) {
    // 執行一個異步操作
    setTimeout(() => {
      let mathNum = Math.ceil(Math.random() * 10); //生成1-10的隨機數
        if (mathNum <= 5) { // 如果數字 ≤ 5,則將promise的狀態值為fullfield,可以理解為“成功了”
           console.log('執行完畢 call resolve()...');
           resolve('數字小于5'); // 執行 resolve 回調函數
         } else { // 如果數字大于5,則將Promise的狀態置為 rejected ,可以理解為“失敗了”
           console.log('執行完畢 call reject()...');
           reject('數字大于5'); // 執行 reject 回調函數
         }
     }, 1000);
  });
  // 返回Promise對象
  return p;
}

在最前面,在控制臺輸出 Promise 的時候,會發現,在 then() 方法里面,是可以傳入兩個參數的:

then()

下面調用 getNumthen() 方法傳入兩個回調函數參數:

getNum()
  .then(function (param) {
    console.log(param);
  }, function (param) {
    console.log(param);
});

執行代碼,會得到以下兩種結果的其中一種:

當隨機數小于等于5的時候,結果如下:

結果1

當隨機數大于5的時候,結果如下:
結果2

也就說,第二個函數參數執行了 reject 這個回調函數
這樣的話,在then() 中也能觸發 reject 這個回調函數,需要作為 then() 方法的第二個參數值傳入
catch的作用
那么 catch() 這個方法是作何使用呢?

  • 作用一:指定 reject 的回調
    catch作用之一和 then() 方法的第二個參數的作用一樣,用來指定 reject 的回調,用法如下:
getNum()
  .then(function (param) {
    console.log(param);
  })
  .catch( function (param) {
    console.log(param);
});

執行代碼發現同then()方法里面的第二個參數一樣。

  • 作用二:在執行resolve的回調(也就是上面then中的第一個參數)時,如果拋出異常了(代碼出錯了),會進入到 catch() ,而不會中斷js的執行
    修改上例的代碼:
getNum()
  .then(function (param) {
    console.log(param);
    console.log(param1); //此處的param1 未定義
  })
  .catch( function (param) {
    console.log(param);
});

resolve 的回調函數邏輯中,輸出了一個未定義的變量。如果不使用 Promise ,會直接報錯,并中斷js的執行。但是在這里,會得到如下的結果:

catch報告異常

也就是拋出異常后進入 catch 了,并且把異常信息作為參數傳遞給了 reject 。即便有錯誤的代碼也不會報錯了。這與try...catch的方法有相同的功能。

3. Promise 的其他用法

在剛開始,控制臺查看 Promise 是什么的時候,發現 Promise 自身還有 race()all() 等方法,下面看看這些方法是做什么用的

1. Promise.all()的用法

Promise的all方法提供了并行執行異步操作的能力,并且在所有異步操作執行完后才執行回調。使用上面定義好的runAsync、runAsync1、runAsync2這三個函數,看下面的例子:

Promise
  .all([runAsync(), runAsync1(), runAsync2()])
  .then(function (result) {
    console.log(result);
  });

all()方法接受一個數組參數,數組中的值最終都返回Promise對象。這樣,數組中的三個異步操作是并行執行的,等這三個異步操作執行完之后才會進入 then 中,可以將其中一個異步執行的時間長一點,發現最終的返回結果的時間,以所有異步操作執行完畢為準。那么,三個異步操作返回的數據結果去哪兒了呢?同樣的,都在 then 中,all()方法回把所有的異步操作的結果放進一個數組中作為then回調函數的參數傳入,所以上面代碼輸出的結果:

all()

  • all()的應用場景:
    有了all,就可以并行執行多個異步操作,并且在一個回調中處理所有的返回數據,有一個場景是很適合用這個的,一些游戲類的素材比較多的應用,打開網頁時,預先加載需要用到的各種資源如圖片、flash以及各種靜態文件。所有的都加載完后,我們再進行頁面的初始化。

2. Promise.race()的用法

上面說了 all() 方法的效果就是“誰最慢,以誰為準進行回調”,那么相對的還有另外一個方法“誰跑的快,以誰為準執行回調”,這就是 race() 方法, race 的用法和 all 一樣,修改下代碼,將三個異步操作的延時時間改為2秒,1秒,3秒,執行race方法:

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

這三個異步操作也是并行執行的。結果是runAsync()1秒后執行完了,此時then里面的就執行了。看下執行結果:

race()

當執行代碼的時候會發現,跟預期的稍微不太一樣,執行了 runAsync1() 之后,并進入到then()中執行 runAsyac()中的resolve回調函數,輸出了結果,但是 runAsync() 和 runAsync2() 仍在執行,只是沒有執行相應的回調函數,那是因為 then() 已經執行完畢了,所以 race() 方法只能拿到最先執行那個異步操作的結果

  • race() 的應用場景
    可以定義一個請求超時的方法,把請求超時的方法和請求都放入 race() 中,如果請求的時間超過設定的時間,則直接報請求超時,下面實現一下:
function request() {
  return new Promise((resolve, reject) => {
      var img = new Image();
      img.src = "https://www.cnblogs.com/xxxx";
      img.onload = () => resolve(img);
    });
}

function timeOut() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('請求超時');
    }, 2000);
  });
}
Promise
  .race([request(), timeOut()])
  .then((result) => {
    // 處理返回的數據
    console.log(result);
    $('div').append(result);
  })
  .catch((reason) => {
    console.log(reason);
});

執行代碼,因為圖片的路徑是隨便寫的,肯定返回不了,2秒鐘后結果顯示:請求超時;
如果更換一個正確的圖片路徑,則會正確的顯示結果,并在界面顯示圖片。
結束。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容

  • Promise 對象 Promise 的含義 Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函...
    neromous閱讀 8,720評論 1 56
  • 本文適用的讀者 本文寫給有一定Promise使用經驗的人,如果你還沒有使用過Promise,這篇文章可能不適合你,...
    HZ充電大喵閱讀 7,322評論 6 19
  • 前言 “盡管我已經在使用promise來處理異步交互了,但是我面對各種resolve和reject、defer等字...
    蚊子爸爸閱讀 623評論 5 10
  • 目錄:Promise 的含義基本用法Promise.prototype.then()Promise.prototy...
    BluesCurry閱讀 1,496評論 0 8
  • 何麗的父親走了十多年了。父親在世的時候,她討厭、疏遠著父親,父親像是她身邊一個熟悉的陌生人。 她想象中的父親,應該...
    啊華的沉淀時光閱讀 335評論 1 1