第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

ES6+ Promise 進階

1. 前言

前兩節(jié)我們學習了 Promise 的用法,并且在上一節(jié)我們動手實現(xiàn)了一個符合 Promise A+ 規(guī)范的簡版 Promise。真正了解了 Promise 底層是怎么來實現(xiàn)的,更好地幫助我們理解 Promise 并對 Promise 的擴張打下了基礎。對 Promise 的擴展會可以解決一些通用的問題,比如使用 Promise.all() 去并發(fā)請求接口。在 node 中還提供了將 callback 類型的 api 轉(zhuǎn)換為 Promise 對象。

本節(jié)我們將繼續(xù)學習 Promise 對象相關API的使用。這些api在我們的實際應用中會經(jīng)常使用,并且可以很好的解決常見的問題。

2. Promise.resolve () 和 Promise.reject ()

前面我們已經(jīng)學習了在 new Promise() 對象時執(zhí)行器會提供兩個回調(diào)函數(shù),一個是 resolve 返回一個立即成功的 Promise,一個是 reject 返回一個立即失敗的 Promise。在執(zhí)行器中需要根據(jù)不同情況調(diào) resolvereject ,如果我們只想返回一個成功或失敗的 Promise 怎么做呢?

Promise 對象上的提供了 Promise.resolve(value)Promise.reject(reason) 語法糖,用于只返回一個成功或失敗的 Promise。下面我們看下它的對比寫法:

const p1 = new Promise(function(resolve, reject){
    reslove(100)
})
const p2 = Promise.resolve(100) //和p1的寫法一樣

const p3 = new Promise(function(resolve, reject){
    reject('error')
})
const p4 = Promise.reject('error') //和p3的寫法一樣

通過上面的對比 Promise.resolve(value) 創(chuàng)建的實例也具有 then 方法的鏈式調(diào)用。這里有個概念就是:如果一個函數(shù)或?qū)ο?,具?then 方法,那么他就是 thenable 對象。

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

Promise.reject(new Error('error')).then(() => {
  // 這里不會走 then 的成功回調(diào)
}, (err) => {
  console.error(err);
});

其實,實現(xiàn) Promise.resolve(value)Promise.reject(reason) 的源碼是很簡單的。就是在 Promise 類上創(chuàng)建 resolvereject 這個兩個方法,然后去實例化一個 Promise 對象,最后分別在執(zhí)行器中的 resolve()reject() 函數(shù)。按照這個思路有如下實現(xiàn)方式:

class Promise {
	...
  resolve(value) {
    return new Promise((resolve, reject) => {
      resolve(value)
    })
  }
  reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason)
    })
  }
}

通過上面的實現(xiàn)源碼我們很容易地知道,這兩個方法的用法。需要注意的是 Promise.resolve(value) 中的 value 是一個 Promise 對象 或者一個 thenable 對象,Promise.reject(reason) 傳入的是一個異常的原因。

3. catch()

Promise 對象提供了鏈式調(diào)用的 catch 方法捕獲上一層錯誤,并返回一個 Promise 對象。catch 其實就是 then 的一個別名,目的是為了更好地捕獲錯誤。它的行為和 Promise.prototype.then(undefined, onRejected) 只接收 onRejected 回調(diào)是相同的,then 第二個參數(shù)是捕獲失敗的回調(diào)。所以我們可以實現(xiàn)一個 catch 的源碼,如下:

class Promise {
  //...
  catch(errorCallback) {
    return this.then(null, errorCallback);
  }
}

從上面的實現(xiàn) catch 的方法我們可以知道,catch 是內(nèi)部調(diào)用了 then 方法并把傳入的回調(diào)傳入到 then 的第二個參數(shù)中,并返回這個 Promise。這樣我們就更清楚地知道 catch 的內(nèi)部原理了,以后看到 catch 可以直接把它看成調(diào)用了 then 的失敗的回調(diào)就行。下面我們看幾個使用 catch 的例子:

let promise = new Promise((resolve, reject) => {
  resolve('100');
})
promise.then((data) => {
  console.log('data:', data);	// data: 100
  throw new Error('error')
}, null).catch(reason => {
  console.log(reason)	// Error: error
})

catch 后還可以鏈式調(diào)用then方法,默認會返回 undefined。也可以返回一個普通的值或者是一個新的 Promise 實例。同樣,在 catch 中如果返回的是一個普通值或者是 resolve,在下一層還是會被 then 的成功回調(diào)所捕獲。如果在 catch 中拋出異常或是執(zhí)行 reject 則會被下一層 then 的失敗的回調(diào)所捕獲。

promise.then((data) => {
  console.log('data:', data);	// data: 100
  throw new Error('error')
}, null).catch(reason => {
  console.log(reason)	// Error: error
  return 200
}).then((value) => {
  console.log(value)	// 200
}, null)

4. finally()

finally 是 ES9 的規(guī)范,它也是 then 的一個別名,只是這個方法是一定會執(zhí)行的,不像上面提到的 catch 只有在上一層拋出異?;蚴菆?zhí)行 reject 時才會走到 catch 中。

Promise.resolve('123').finally(() => {
  console.log('100')	// 100
})

知道 finally 是 then 的一個別名,那我們就知道在它后面也是可以鏈式調(diào)用的。

Promise.resolve('123').finally(() => {
  console.log('100')
  return 200
}).then((data) => {
  console.log(data)	// 123
})

需要注意的是在 finally 中返回的普通值或是返回一個 Promise 對象,是不會傳到下一個鏈式調(diào)用的 then 中的。如果 finally 中返回的是一個異步的 Promise 對象,那么鏈式調(diào)用的下一層 then 是要等待 finally 有返回結果后才會執(zhí)行:

Promise.resolve('123').finally(() => {
  console.log('100')
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(100)
    }, 3000)
  })
}).then((data) => {
  console.log(data)	// 123
})

執(zhí)行上面的代碼,在 then 中打印的結果會在 3 秒后執(zhí)行。這也說明了 finally 有類似 sleep 函數(shù)的意思。

finally 是 ES9 的規(guī)范,在不兼容 ES9 的瀏覽器中就不能使用這個 api,所以我們可以在 Promise 對象的原型上增加一個 finally 方法。

Promise.prototype.finally = function(callback) {
  return this.then((value) => {
    return Promise.resolve(callback()).then(() => value);
  }, (err) => {
    return Promise.reject(callback()).catch(() => {throw err});
  })
}

因為 finally 是一定會執(zhí)行的,所以 then 中的成功和失敗的回調(diào)都需要執(zhí)行 finally 的回調(diào)函數(shù)。使用 Promise.resolve(value)Promise.reject(reason) 去執(zhí)行 finally 傳入的回調(diào)函數(shù),然后使用 then 和 catch 來返回 finally 上一層返回的結果。

3. Promise.all () 和 Promise.race ()

在前端面試中經(jīng)常會問這兩個 api 并做對比,因為它們的參數(shù)都是傳入一個數(shù)組,都是做并發(fā)請求使用的。

3.1 Promise.all()

Promise.all() 特點是將多個 Promise 實例包裝成一個新的 Promise 實例,只有同時成功才會返回成功的結果,如果有一個失敗了就會返回失敗,在使用 then 中拿到的也是一個數(shù)組,數(shù)組的順序和傳入的順序是一致的。

const p1 = Promse.resolve('任務1');
const p2 = Promse.resolve('任務2');
const p3 = Promse.reject('任務失敗');

Promise.all([p1, p2]).then((res) => {
  console.log(res);		// ['任務1', '任務2']
}).catch((error) => {
  console.log(error)
})

Promise.all([p1, p3, p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)      // 任務失敗
})

Promise.all() 在處理多個任務時是非常有用的,比如 Promise 基礎 一節(jié)中使用 Promise.all() 并發(fā)的請求接口的案例,我們希望得到所以接口請求回來的數(shù)據(jù)之后再去做一些邏輯,這樣我們就不需要維護一個數(shù)據(jù)來記錄接口請求有沒有完成,而且這樣請求的好處是最大限度地利用瀏覽器的并發(fā)請求,節(jié)約時間。

3.2 Promise.race()

Promise.race()Promise.all() 一樣也是包裝多個 Promise 實例,返回一個新的 Promise 實例,只是返回的結果不同。Promise.all() 是所有的任務都處理完才會得到結果,而 Promise.race() 是只要任務成功就返回結果,無論結果是成功還是失敗。

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('任務1成功...');
  }, 1000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('任務2成功...');
  }, 1500)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('任務失敗...');
  }, 500)
})

Promise.race([p1, p2]).then((res) => {
  console.log(res);   // 任務1成功...
}).catch((err) => {
  console.log(err);
})

Promise.race([p1, p2, p3]).then((res) => {
  console.log(res)
}).catch((err) => {
  console.log(err)  // 任務失敗...
})

上面的實例代碼充分的展示了 Promise.race() 特性,在實際的開發(fā)中很少用到這個 api,這個 api 能做什么用呢?其實這個 api 可以用在一些請求超時時的處理。

當我們?yōu)g覽網(wǎng)頁時,突然網(wǎng)絡斷開或是變得很差的情況下,可以用于提示用戶網(wǎng)絡不佳,這也是一個比較常見的情況。這個時候我們就可以使用 Promise.race() 來處理:

const request = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('請求成功...');
  }, 3000);
})
const timeout = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('請求超時,請檢查網(wǎng)絡...');
  }, 2000);
})

Promise.race([request, timeout]).then(res => {
  console.log(res);
}, err => {
  console.log(err);   // 請求超時,請檢查網(wǎng)絡...
})

上面的代碼中定義了兩個 Promise 實例,一個是請求實例,一個是超時實例。請求實例當 3 秒的時候才會返回,而超時設置了 2 秒,所以會先返回超時的結果,這樣就可以去提醒用戶了。

3.3 實現(xiàn) Promise.all ()

面試題:實現(xiàn)一個 Promise.all() 方法。

前面我們說到了 thenable 對象,也就是判斷一個值是不是 Promise 對象,就是判斷它是函數(shù)或?qū)ο?,并具?then 方法。

const isPromise = (val) => {
  if (typeof val === "function" || (typeof val == "object" && val !== null)) {
    if (typeof val.then === "function") {
      return true;
    }
  }
  return false;
};

Promise.all() 會接收一個數(shù)組,數(shù)組的每一項都是一個 Promise 實例,并且它的返回結果也是一個 Promise,所以我們需要在內(nèi)部 new 一個 Promise 對象,并返回。在執(zhí)行器中我們的目標是:

  1. 當有實例中有錯誤或拋出異常時,就要執(zhí)行執(zhí)行器中的 reject;
  2. 沒有錯誤時,只有所有的實例都成功時才會執(zhí)行執(zhí)行器中的 resolve。

基于這兩點,有如下步驟:

  1. 內(nèi)部創(chuàng)建一個計數(shù)器,用于記住已經(jīng)處理的實例,當計數(shù)的值和傳入實例的數(shù)組長度相等時,執(zhí)行執(zhí)行器中的 resolve;
  2. 創(chuàng)建一個用于存放實例返回結果的數(shù)組;
  3. 處理實例的結果有兩種:一種返回的是普通值、一種返回的是 Promise 對象,然后分別處理;
  4. 返回普通值結果時直接存放到數(shù)組中即可;
  5. 返回的是一個 Promise 對象時,就需要調(diào)用這個實例上的 then 方法得到結果后在存放到結果數(shù)組中去。

根據(jù)上面的五個步驟基本就可以把 Promise.all() 實現(xiàn)出來了,具體代碼如下:

Promise.all = function(arr) {
  return new Promise((resolve, reject) => {
    let num = 0;  // 用于計數(shù)
    const newArr = [];  // 存放最終的結果

    function processValue(index, value) {	// 處理Promise實例傳入的結果
      newArr[index] = value;
      if (++num == arr.length) {	// 當計數(shù)器的值和處理的 Promise 實例的長度相當時統(tǒng)一返回保護所以結果的數(shù)組
        resolve(newArr);
      }
    }

    for (let i = 0; i < arr.length; i++) {
      const currentValue = arr[i];  // Promise 實例
      if (isPromise(currentValue)) {
        currentValue.then((res) => {
          processValue(i, res);
        }, reject)
      } else {
        processValue(i, currentValue);
      }
    }
  });
}

上面的代碼已經(jīng)實現(xiàn)了 Promise.all() 方法,可以使用下面的例子進行測試。

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("任務1成功...");
  }, 1000);
});

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("任務2成功...");
  }, 500);
});

Promise.all([p1, p2]).then((res) => {
  console.log(res)
})

4. 小結

本節(jié)學習了根據(jù) Promise 衍生出的相關 api 的使用,已經(jīng)每個 api 基本都給出了實現(xiàn)源碼,理解這些源碼會讓我們更加深刻地理解 Promise,在實際的開發(fā)過程中達到游刃有余。到此我們花了三節(jié)的時間由淺入深來介紹 Promise,花些時間來徹底弄懂這些知識點,對于我們以后學習其他的異步解決方案有更好的理解。