当前位置:网站首页>硬核解析Promise對象(這七個必會的常用API和七個關鍵問題你都了解嗎?)

硬核解析Promise對象(這七個必會的常用API和七個關鍵問題你都了解嗎?)

2022-04-23 18:12:00 小傑學前端

目錄

一、Promise的理解與使用

Promise的概念

Promise的使用

回調地獄問題

解决方案

Promise的基本用法

Promise實踐初體驗

fs模塊讀取文件

使用 promise 封裝 ajax 异步請求

promise對象狀態屬性

promise對象結果值屬性

小案例

二、Promise API

Promise 構造函數: Promise (excutor) {}

Promise.prototype.then()

Promise.prototype.catch()

Promise.resolve() 

Promise.reject()  

Promise.all()    

Promise.race() 

三、Promise關鍵問題 

1.如何修改對象的狀態

2.能否執行多個回調

3.改變狀態與執行回調的順序問題

4.then方法返回結果由什麼决定 

5.串聯多個任務

6.异常穿透

7.如何終斷Promise鏈 


一、Promise的理解與使用

Promise的概念

Promise 是异步編程的一種解决方案,而我們之前的解决方案是啥呢?沒錯,就是回調函數。Promise要比它更合理和更强大。

這樣解釋還不够清楚明白,那Promise到底是個啥呢 

簡單說就是一個容器,裏面保存著某個未來才會結束的事件(通常是一個异步操作)的結果。

通俗講,Promise翻譯過來就是承諾,它是對未來事情的承諾,承諾不一定能完成(就像你答應你女朋友未來會給她買包,只是想先應付一下,以後買不買不一定呢),但是無論是否能完成都會有一個結果。

Promise的使用

Promise 是异步編程的一種解决方案,主要用來解决回調地獄的問題,可以有效减少回調嵌套。

從語法上說:Promise就是一個構造函數

從功能上說:Promise對象用來封裝一個异步操作並可以獲取其成功/失敗的返回值

首先我們先想一下都有哪些是异步操作 

fs文件操作    數據庫操作   ajax請求操作   定時器

上面都是异步操作

fs文件操作 require('fs').readFile('./xiaojie.txt',(err,data)=>{})
數據庫操作   $.get('/server',(data)=>{})
定時器   setTimeout(() => {}, 2000);

在promise前,我們進行异步操作時,用的都是純回調函數的方式來進行的,那我們為什麼使用promise呢,使用回調函數不行嗎 

注意,這是最重要的,面試必問

  因為Promise支持鏈式調用,可以解决回調地獄問題  

回調地獄問題

那麼什麼是回調地獄呢 為啥要解决他呢 Promise又是怎麼解决的呢 

下面我們一個一個來解决:

首先我們看下面這段代碼,我們想分三次輸出數字,每次間隔一秒鐘,那我們肯定得這麼做:

setTimeout(function () {
    console.log(111);
    setTimeout(function () {
        console.log(222);
        setTimeout(function () {
            console.log(333);
        }, 1000);
    }, 1000);
}, 1000);

代碼嵌套,在一個複雜的程序當中,是非常不便於閱讀的並且不便於异常處理,因為在每一層我們都需要對錯誤進行异常處理,可能會寫很多重複的代碼而且會讓縮進格式變得非常冗贅,當嵌套層數過多,會出現“回調地獄”。在這裏就體現出用一種新的方式書寫代碼有多重要。用promise方法去處理這類問題,無疑會更合適。

解决方案

  promise鏈式調用  

new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(111);
        resolve();
    }, 1000);
}).then(() => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(222);
            resolve();
        }, 1000);
    });
}).then(() => {
    setTimeout(() =>{
        console.log(333);
    }, 1000);
});

這樣最後的輸出效果是相同的,而且會把嵌套的代碼語句書寫改變為按序書寫,但不改變其執行的异步過程,這正是promise方法的作用及優點。 

Promise的基本用法

ES6 規定,Promise 是一個構造函數,即然是構造函數那他就可以進行對象實例化。

promise在對象實例化的時候需要接受參數,這個參數是一個函數類型的值

該函數的兩個參數分別是 resolve 和 reject 。

resolve是解决的意思,reject是拒絕的意思。

這兩個都是函數類型的數據

當异步任務成功時調用resolve,當异步任務失敗時調用reject

下面代碼創造了一個Promise實例。

const p = new Promise((resolve, reject) => {
  // ... some code
    if (/* 异步操作成功 */){
        resolve(value);
    } else {
        reject(error);
    }
});

  resolve() 在調用完後,可以將promise對象(p)的狀態設置為成功  

  reject() 相反,調用完後,可以將promise對象(p)的狀態設置為失敗  

我們創建完promise實例後需要調用then方法,他在執行時需要接收兩個參數,而且兩個參數都是函數類型的值

p.then((value) => {},(error) => {});

第一個參數是對象成功時的回調,第二個參數是對象失敗時的回調。

這兩個參數都是可選的,不一定要提供。它們都接受 Promise 對象傳出的值作為參數。
通俗的說,如果promise對象狀態成功就執行第一個回調函數,如果狀態失敗就執行第二個回調函數

Promise實踐初體驗

我們先不展開講解Promise的各種API和原理,先做兩個實踐應用,簡單體驗一下Promise。

fs模塊讀取文件

這個需要我們安裝node.js環境,fs模塊的作用就是可以對計算機的硬盤進行讀寫操作

我們的案例要求是這樣的:

讀取當前目錄下的nba.txt文件裏的內容:

  傳統回調函數形式: 

//引入fs模塊
const fs = require('fs');
fs.readFile('./nba.txt', (err, data) => {
    // 如果出錯 則拋出錯誤
    if(err)  throw err;
    //沒有出錯就輸出文件內容
    console.log(data.toString());
});
PS D:\用戶目錄\Desktop\home\html (2)\4.20> node zyj.js
勇士總冠軍

這樣的話我們的nba.txt裏的內容就輸出出來了

  Promise封裝形式: 

//封裝一個函數 mineReadFile 讀取文件內容
//參數:  path  文件路徑
//返回:  promise 對象

function mineReadFile(path){
   return new Promise((resolve, reject) => {
       //讀取文件
       require('fs').readFile(path, (err, data) =>{
           //判斷
           if(err) reject(err);
           //成功
           resolve(data);
       });
   });
}

mineReadFile('./nba.txt')
.then(value=>{
   //輸出文件內容
   console.log(value.toString());
}, error=>{
   console.log(error);
});

如果成功的話data就是一個buffer需要tostring方法把它轉化為字符串,如果出錯就把錯誤的對象打印出來

當我們輸出正常時:

PS D:\用戶目錄\Desktop\home\html (2)\4.20> node zyj.js
勇士總冠軍

我們故意把路徑打錯,看看錯誤會輸出什麼:

PS D:\用戶目錄\Desktop\home\html (2)\4.20> node zyj.js
[Error: ENOENT: no such file or directory, open 'D:\用戶目錄\Desktop\home\html (2)\4.20\na.txt'] {
  errno: -4058,
  code: 'ENOENT',
  syscall: 'open',
  path: 'D:\\用戶目錄\\Desktop\\home\\html (2)\\4.20\\na.txt'
}

輸出的就是error這個錯誤對象裏的內容

使用 promise 封裝 ajax 异步請求

<script >
/*可複用的發 ajax 請求的函數: xhr + promise*/
function promiseAjax(url) {
     return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open("GET", url);
            xhr.send();
            xhr.onreadystatechange = () => {
              if (xhr.readyState !== 4) return;
              // 請求成功, 調用 resolve(value)
              if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr.response);
              } else { // 請求失敗, 調用 reject(reason)
                reject(xhr.status);
              }
            }
          })
        }
promiseAjax('http://www.liulongbin.top:3006/api/getbooks').then(data => {
           console.log('顯示成功數據', data)
        },
        error => {
           alert(error)
        }) 
</script>

輸出結果:

當我們故意把地址打錯:

promise對象狀態屬性

Promise 對象的狀態不受外界影響。Promise 對象有三種狀態:
  pending (進行中)、fulfilled (已成功) 和 rejected (已失敗)。

一旦狀態改變,就不會再變,任何時候都可以得到這個結果。

Promise對象的狀態改變,只有兩種可能:從 pending 變為 fulfilled(resolved)和從 pending 變為 rejected。

只要這兩種情况發生,狀態就凝固了,不會再變了,會一直保持這個結果。

只有异步操作的結果,可以决定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,錶示其他手段無法改變。

如果改變已經發生了,你再對Promise對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

promise對象結果值屬性

這是 promise 實例對象中的另一個屬性 [promiseResult] ,保存著异步任務 [成功/失敗] 的結果

resolve和reject兩個方法是可以對實例對象的結果屬性進行修改,除了他倆誰也改不了

小案例

在對Promise有個大概了解後,我們看一下下面的代碼:

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
  });
}

timeout(100).then((value) => {
  console.log(value);
});

分析:

上面代碼中,timeout 方法返回一個Promise實例對象,錶示一段時間以後才會發生的結果。過了指定的時間(ms參數)以後, 計時器會調用 resolve(),並且把結果值(done)傳進來,在調用完後,可以將promise對象(p)的狀態設置為成功 ,他的狀態改變了就會觸發then方法綁定的回調函數,把傳進來的結果值(done)輸出出來。

二、Promise API

Promise 構造函數: Promise (excutor) {}

(1) executor 函數: 執行器 (resolve, reject) => {}

(2) resolve 函數: 內部定義成功時我們調用的函數 value => {}

(3) reject 函數: 內部定義失敗時我們調用的函數 reason => {}

重點:executor 會在 Promise 內部立即同步調用 

我們來看下面這段代碼

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

p.then(value=> {
  console.log('andy');
});

console.log('Hi!');

// jack
// Hi!
// andy

上面代碼中,Promise 新建後立即執行,所以首先輸出的是 'jack'。然後,then方法指定的回調函數,將在當前脚本所有同步任務執行完才會執行,所以再輸出 'Hi','andy' 最後輸出。

實際上,這個運行結果相關知識點是 [ 宏任務與微任務 ]  ,單獨梳理在下方:

  • 宏任務: setTimeout , setInterval , Ajax , DOM事件
  • 微任務: Promise async/await
  • 微任務執行時機比宏任務要早(先記住)

下面我們看一道面試題:

console.log(100);

setTimeout(()=>{
    console.log(200);
})

setTimeout(()=>{
    console.log(201);
})

Promise.resolve().then(()=>{
    console.log(300);
})

console.log(400);

// 100 400 300 200 201

大家都做對了嗎,如果沒有做對的話,再結合一下JS的運行機制就可以輕松理解了:

  • 所有的同步任務都在主線程上執行,行成一個執行棧。
  • 除了主線程之外,還存在一個任務列隊,只要异步任務有了運行結果,就在任務列隊中植入一個時間標記。
  • 主線程完成所有任務(執行棧清空),就會讀取任務列隊,先執行微任務隊列在執行宏任務隊列。
  • 重複上面三步。

Promise.prototype.then()

then方法是定義在原型對象Promise.prototype上的。它的作用是為 Promise 實例添加狀態改變時的回調函數

then方法方法返回的是一個新的Promise實例 

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

let p = new Promise((resolve, reject) => { 
  setTimeout(resolve, 1000, 'jack'); 
}); 
p.then(value => {
  console.log(value); 
  return 'andy';
}).then(value => console.log(value));
//jack
//andy

then()方法會返回一個新的Promise對象, 所以then後面可以繼續跟一個then方法進行鏈式調用, 如果then方法的回調函數有返回數據,那麼這個數據會作為下一個then的回調函數的參數,這塊知識點涉及到鏈式調用,在Promise關鍵問題第五個串聯多個任務裏有詳細講解。大家在這裏先做一個簡單了解。

采用鏈式的then, 會等待前一個Promise狀態發生改變才會被調用

then的回調函數也可能會返回一個Promise對象,這個時候就會覆蓋then自己返回的Promise對象,這時後一個回調函數,就會等待該Promise對象的狀態發生變化,才會被調用。

let p = new Promise((resolve, reject) => { 
  setTimeout(resolve, 1000, 'jack'); 
}); 
p.then(value => {
  console.log(value); 
  return new Promise((resolve,reject) => {
    setTimeout(resolve,1000,'andy');
  });
}).then(value => console.log(value));
//jack
//andy

上面代碼中,第一個then方法指定的回調函數,返回的是另一個Promise對象。這時,第二個then方法指定的回調函數,就會等待這個新的Promise對象狀態發生變化。

因為第二個回調函數有一個定時器,所以一秒鐘後狀態變為resolved,第二個then方法看到狀態改變了才會去執行log輸出語句

Promise.prototype.catch()

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

let p = new Promise((resolve, reject) => { 
  reject('what is the matter');
}); 
p.catch(error => {
  console.log(error);
})
//what is the matter

如果Promise 對象狀態變為rejected,就會調用 catch() 方法指定的回調函數,處理這個錯誤。

let p = new Promise((resolve, reject) => { 
  throw new Error('拋出了錯誤')
}); 
p.catch(error => {
  console.log(error);
})
//Error: 拋出了錯誤

上面代碼中,Promise拋出一個錯誤,同樣會被 catch() 方法指定的回調函數捕獲。

  值得注意的是:如果 Promise 狀態已經變成 resolved ,再拋出錯誤是無效的。 

let p = new Promise((resolve, reject) => { 
  resolve('success');
  throw new Error('拋出了錯誤')
}); 
p.then(value => {
  console.log(value);
}).catch(error => {
  console.log(error);
})
//success

上面代碼中,拋出錯誤在 resolve 語句的後面,那就不會被捕獲,等於沒有拋出。因為 Promise 的狀態一旦改變,就永久保持該狀態,不會再變了。

Promise 對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個 catch 語句捕獲。

我們知道每個then方法都會產生一個新的Promise實例,如果一個Promise對象後面緊跟著多個then方法,它們之中任何一個拋出的錯誤,都會被最後一個catch()捕獲。

  重點:所以,不要在 then() 方法裏面定義 Reject 狀態的回調函數(即 then 的第二個參數),最   好使用catch方法,這樣可以捕獲前面then方法執行中的錯誤。

Promise.resolve()  

有時需要將現有對象轉為 Promise 對象,Promise.resolve() 方法就起到這個作用。

let p = Promise.resolve('jack');
console.log(p);

打開控制臺,看看上面這段代碼輸出了什麼:

他的結果是一個Promise對象,而且狀態為成功,成功的值為我們傳入的參數 'jack' 。

Promise.resolve() 等價於下面的寫法:

Promise.resolve('jack')
// 等價於
new Promise(resolve => {resolve('jack')})

Promise.resolve() 方法的參數分成兩種情况:

參數是一個Promise實例:

如果參數是 Promise 實例,那麼Promise.resolve() 將不做任何修改、原封不動地返回這個實例。

let p1 = Promise.resolve('jack');
let p2 = Promise.resolve(p1);
console.log(p2);
//Promise {[[PromiseState]]: 'fulfilled', [[PromiseResult]]: 'jack'}

參數不是Promise對象或沒有任何參數:

Promise.resolve() 方法返回一個新的 Promise 對象,狀態為 resolved

const p = Promise.resolve();

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

上面代碼的變量 p 就是一個 Promise 對象。

需要注意的是,立即 resolve() 的 Promise 對象,是在本輪“事件循環”(event loop)的結束時執行,而不是在下一輪“事件循環”的開始時。 

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

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

console.log('david');

// david
// andy
// jack

上面代碼中,setTimeout( fn , 0) 下一輪“事件循環”開始時執行,Promise.resolve() 在本輪“事件循環”結束時執行,console.log('david') 是立即執行,因此最先輸出。

Promise.reject()   

Promise.reject(reason) 方法也會返回一個新的 Promise 實例,該實例的狀態為 rejected 。

let p1 = Promise.reject('出錯啦');
console.log(p1);
//Promise { <rejected> '出錯啦' }
const p = Promise.reject('出錯啦');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯啦'))

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

即便我們傳入的參數是一個成功的Promise對象,他的結果也是失敗:

let p1 = Promise.reject(new Promise((resolve, reject) => {
    resolve('ok');
}));
console.log(p1);
//Promise { <rejected> Promise { 'ok' } }

此時我們的Promise對象p1的狀態是失敗,它失敗的結果是我們傳入的這個值,也就是這個成功的Promise對象,簡單理解就是,不論怎樣,Promise.reject()返回的狀態一定是rejected 

Promise.all()    

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

const p = Promise.all([p1, p2, p3]);
  • Promise.all() 方法接受一個數組作為參數,
  • p1, p2, p3 都是 Promise 實例,如果不是,就會先調用Promise.resolve方法,將參數轉為 Promise 實例,再進一步處理。
let p1 = Promise.resolve('jack');
let p2 = Promise.resolve(111);
const p3 = Promise.all([p1,p2,'andy']);
console.log(p3);

這裏我們定義了兩個狀態為成功的Promise對象 p1和p2,我們想通過Promise.all() 方法將多個 Promise 實例,包裝成一個新的 Promise 實例 p3,但是參數數組裏的最後一個元素並不是Promise對象而是一個字符串,那麼 p3 會輸出什麼呢?

輸出結果如上圖所示,就如我們前面提到的,如果數組內有元素不是Promise實例,會先調用Promise.resolve方法,將參數轉為 Promise 實例,再進行處理。

p 的狀態由 p1, p2, p3 决定,分成兩種情况:

1. 只有 p1, p2, p3 的狀態都變成 fulfilled ,p 的狀態才會變成 fulfilled ,此時 p1, p2, p3 的返回值組成一個數組,傳遞給p的回調函數。

2. 只要 p1, p2, p3 之中有一個為 rejected,p 的狀態就變成 rejected ,此時第一個被 reject 的實例的返回值,會傳遞給p的回調函數。

下面我們來分析一段代碼:

const p1 = new Promise((resolve, reject) => {
  resolve('success');
}).then(value => value).catch(reason => reason);

const p2 = new Promise((resolve, reject) => {
  throw new Error('報錯了');
}).then(value => value).catch(reason => reason);

Promise.all([p1, p2]).then(value => {console.log(value)})
.catch(reason => {console.log(reason)});

上面代碼中,p1會resolved,p2首先會rejected,但是p2有自己的catch方法,該方法返回的是一個新的 Promise 實例,p2指向的實際上是這個實例。該實例執行完catch方法後,也會變成resolved,導致Promise.all() 方法參數裏面的兩個實例都會resolved,因此會調用then方法指定的回調函數,而不會調用catch方法指定的回調函數。

下面是這段代碼的輸出結果:

如果p2沒有自己的catch方法,就會調用 Promise.all() 的 catch 方法,這個時候輸出結果就是這樣的:

Promise.race()

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

const p = Promise.race([p1, p2, p3]);

上面代碼中,只要p1 , p2 , p3 之中有一個實例率先改變狀態,p的狀態就跟著改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。 

我們分析一下下面一段代碼應該輸出什麼:

let p1 = new Promise((resolve,reject)=>{
  setTimeout(() => {
    resolve('ok');
  }, 1000);
})
let p2 = Promise.resolve('success');
let p3 = Promise.resolve('oh year');
const result = Promise.race([p1,p2,p3]);
console.log(result);

我們先看一下p1,p2,p3他們三個誰先改變狀態,如果p1先改變狀態,那 result 結果就是由p1决定的,如果p1成功那麼result就成功,它成功的結果就是result成功的結果。但是因為在我們的例子裏p1是一個定時器,是一個异步任務,那肯定不是p1,順著往下排,p1不行那就肯定p2先改變狀態,我們在控制臺看看輸出的結果:

因為p2的狀態是成功,所以result的狀態就是fulfilled,因為p2結果是success,所以result結果也是result。



三、Promise關鍵問題 

1.如何修改對象的狀態

  1. resolve(value): 如果當前是 pending 就會變為 resolved
  2. reject(reason): 如果當前是 pending 就會變為 rejected
  3. 拋出异常: 如果當前是 pending 就會變為 rejected(可以拋一個Error實例對象)

2.能否執行多個回調

當 promise 改變為對應狀態時都會調用

我們看下面這段代碼:

let p = new Promise((resolve, reject) => {  resolve('OK');});

p.then(value => { console.log(value); });
p.then(value => { console.log(value+' again')});

我們給Promise對象p制定了兩個回調,那它都會執行麼?我們看下控制臺:

控制臺順序打印了兩個回調裏的輸出,當且僅當Promise對象改變為對應狀態時才會調用。

如果在Promise內部不調用resolve,那他的狀態就是pending,這樣我們沒有改變狀態所以一個then回調也不會執行

如果把這段代碼改成這樣,他還會是一樣的結果嗎:

let p = new Promise((resolve, reject) => {  resolve('OK');});

p.then(value => {  console.log(value);}).then(value => { console.log(value+' again');});

先看一下輸出結果:

為什麼第二個回調裏的value是undefined呢? 

因為在上面then方法的詳解中提到過then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)

如果then方法的回調函數有返回數據,那麼這個數據會作為下一個then的回調函數的參數,但是我們上面例子裏的第一個then裏沒有return數據,所以第二個then裏就不知道value是誰,如果我們在第一個then裏加上return value,這樣輸出的結果就和原來一樣啦。

3.改變狀態與執行回調的順序問題

都有可能, 正常情况下是先指定回調再改變狀態, 但也可能先改狀態再指定回調

當Promise對象的執行器當中的任務是同步任務,直接調用resolve,這種情况就是先改變Promise對象的狀態,然後再去指定回調

如下例代碼:

let p = new Promise((resolve,reject) => {
     resolve('success');
});
p.then(value => {console.log(value);});

那什麼時候then方法先執行,然後resolve改變狀態後執行呢?

就是當執行器函數中是异步任務的時候, 也就是說,我改變狀態需要等待一段時間,需要進入對應的异步任務隊列去執行的時候,在這種情况下就是then先執行,resolve改變狀態後執行

如下例代碼:

let p = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('success');
    }, 1000);
});
p.then(value => {console.log(value);});

我們總結一下:

​先指定回調再改變狀態(异步):

  1. 先指定回調
  2. 再改變狀態
  3. 改變狀態後才進入异步隊列執行回調函數

先改狀態再指定回調(同步):

  1. 改變狀態
  2. 指定回調 並馬上執行回調

那麼我們如何先改狀態再指定回調呢?

注意:指定並不是執行

  • 在執行器中直接調用 resolve()/reject() ,不使用定時器等方法,執行器內直接同步操作
  • 延遲更長時間才調用 then() ,在 .then() 這個方法外再包一層例如延時器這種方法

那我們什麼時候才能得到數據? 

  • 如果先指定的回調, 那當狀態發生改變時, 回調函數就會調用, 得到數據
  • 如果先改變的狀態, 那當指定回調時, 回調函數就會調用, 得到數據

簡單地說,如果Promise對象的執行器裏是异步任務,比如說輸出語句在定時器裏的話,就先指定回調,但是指定不是執行。也得等Promise對象的狀態發生改變時,才回去調用回調函數。

4.then方法返回結果由什麼决定 

前面我們說了,then方法方法返回的是一個新的Promise實例 ,那新的Promise對象的狀態由什麼來决定呢,它的返回結果的特點是什麼呢?

簡單錶達:新Promise對象的狀態由then()指定的回調函數執行的結果决定

詳細錶達:

1. 如果拋出异常, 新 promise 變為 rejected, reason 為拋出的异常

代碼示例:

let p = new Promise((resolve,reject) => {
    resolve('success');
});
let result = p.then(value => {
     throw '出錯啦';
})
console.log(result);

輸出結果:

2.如果返回的是非 promise 的任意值, 新 promise 變為 resolved, value 為返回的值

代碼示例:

let p = new Promise((resolve,reject) => {
    resolve('success');
});
let result = p.then(value => {
    return 'jack';
})
console.log(result);

輸出結果:

 

3.如果返回的是另一個新 promise, 此 promise 的結果就會成為新 promise 的結果

代碼示例:

let p = new Promise((resolve,reject) => {
    resolve('success');
});
let result = p.then(value => {
    return new Promise((resolve,reject) => {
        reject('no');
    })
})
console.log(result);

輸出結果:

5.串聯多個任務

1. promise 的 then()返回一個新的 promise, 可以看成 then()的鏈式調用

2. 通過 then 的鏈式調用串連多個同步/异步任務,這樣就能用then()將多個同步或异步操作串聯成一個同步隊列

 我們先看下面這段代碼:

let p = new Promise((resolve,reject) => {
     setTimeout(() => {
          resolve('ok');
     },1000)
});
p.then(value => {
     return new Promise((resolve,reject) => {
          resolve('success');
     })
}).then(value => {
     console.log(value);
})

輸出結果:

因為第一個then返回的是一個新的Promise對象, 那麼這個 promise 的結果就會成為新 promise 的結果,所以最後會輸出success。

接下來我們對上面的代碼再加一個then回調:

let p = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('ok');
    },1000)
});
p.then(value => {
    return new Promise((resolve,reject) => {
        resolve('success');
    })
}).then(value => {
    console.log(value);
}).then(value => {
    console.log(value);
})

最後這個value的輸出會是什麼呢? 答案是 undefined

我們知道then的返回結果是一個Promise對象,它這個Promise對象的狀態是由它指定的回調函數的返回值來决定的,第二個then的回調函數的返回值是空,就是undefined,undefined顯然不是一個Promise類型的對象,我們之前說過,如果返回的是非 promise 的任意值, 新 promise 變為 resolved,也就是狀態是成功的,那他的成功的結果就是返回的結果undefined。因為第二個then返回的Promise對象成功了,就會執行最後一個then的第一個回調,並且輸出他成功的結果,也就是undefined。這就是then的鏈式調用

6.异常穿透

  • 當使用 promise 的 then 鏈式調用時, 可以在最後指定失敗的回調
  • 前面任何操作出了异常, 都會傳到最後失敗的回調中處理

示例代碼:

let p = new Promise((resolve,reject) => {
     setTimeout(() => {
          reject('出錯啦');
     },1000)
});
p.then(value => {
     console.log(000);
}).then(value => {
     console.log(111);
}).then(value => {
     console.log(222);
}).catch(error => {
     console.warn(error);
})

我們先看一下輸出結果:

他輸出的結果剛好是第一個Promise當中他失敗的結果的值,中間的環節我們不需要管,不用指定失敗的回調,只需要在最後指定一個失敗的回調就可以處理失敗的結果。這種現象就屬於异常穿透。

比如我們在中間環節拋出一個錯誤,看看最後的catch能不能捕獲到 :

let p = new Promise((resolve,reject) => {
     setTimeout(() => {
          resolve('ok');
     },1000)
});
p.then(value => {
     throw '出錯啦';
}).then(value => {
     console.log(111);
}).then(value => {
     console.log(222);
}).catch(error => {
     console.warn(error);
})

輸出結果:

第一個then回調裏拋出的錯誤是由最後的catch方法的回調來處理的,這就是异常穿透。

7.如何終斷Promise鏈 

當promise狀態改變時,他的鏈式調用都會生效,那如果我們有這個一個實際需求:我們有4個then(),但其中有條件判斷,如當我符合或者不符合第2個then條件時,要直接中斷鏈式調用,不再走下面的then,該如何?

 方法: 在該回調函數中返回一個 pending 狀態的 Promise 對象 

示例代碼:

let p = new Promise((resolve,reject) => {
     setTimeout(() => {
          resolve('ok');
     },1000)
});
p.then(value => {
     console.log(111);
}).then(value => {
     console.log(222);
     return new Promise(()=>{})
}).then(value => {
     console.log(333);
}).then(value => {
     console.log(444);
})

輸出結果:

示例分析:

中斷Promise鏈的方法有且只有一種方式,那就是必須返回一個pending狀態的Promise對象。因為如果返回的是一個成功的Promise對象,就算返回的結果都是undefined但是then裏的回調還是會執行,唯獨只有pending狀態的Promise對象不一樣,因為如果回調裏返回一個pending,那麼這個then返回的也是狀態為pending的Promise對象,如果第一個(本題中)then返回的是pending狀態的Promise對象,那後續的then方法的回調就都不能執行,因為狀態沒有改變,狀態不改變就都不能執行。

  簡單地說:把當前狀態變成pending,那麼後面的then和catch就不執行  

  到這裏我們Promise的第一部分就完成了,我們學習了Promise相關的API和一些關鍵問題,但是這些對於我們去大廠面試來說還遠遠不够。Promise的第二部分我們將試著去自定義封裝Promise。我們先學會如何怎麼去使用它,再從原理入手學著怎麼去實現它。

希望看到這裏的同學能够來個一鍵三連支持一下小傑同學!

版权声明
本文为[小傑學前端]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204231811052703.html