前端JavaScript之Promise

1、什麼是Promise

Promise 是異步編程的一種解決方案。ES6中已經提供瞭原生 Promise 對象。一個 Promise 對象會處於以下幾種狀態(fulfilledrejected兩種狀態一旦確定後不會改變):

  • 待定(pending): 初始狀態,既沒有被兌現,也沒有被拒絕。
  • 已兌現(fulfilled): 意味著操作成功完成。
  • 已拒絕(rejected): 意味著操作失敗。

2、基本用法

Promise 對象是一個構造函數,用來創建 Promise 實例,它接收兩個參數 resolve reject

  • resolve 的作用是將 Promise 對象的狀態從 pending 變為 fulfilled ,在異步操作成功時調用,並將異步操作的結果,作為參數傳遞出去。
  • reject 的作用是將 Promise 對象的狀態從 pending 變為 rejected ,在異步操作失敗時調用,並將異步操作報出的錯誤,作為參數傳遞出去。
const promise = new Promise(function(resolve, reject) {
  // ... 
  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});


Promise 實例生成以後,使用 then 方法分別指定 fulfilled 狀態和 rejected 狀態的回調函數。

  • then 接收兩個參數,第一個是 Promise 對象的狀態變為 fulfilled 時的回調函數,第二個是狀態變為 rejected 時的回調函數。
  • catch 接收 Promise 對象的狀態變為 rejected 時的回調函數。
promise.then(function (value){
 // ....
},function (err){
 // .... err
})
  
promise.then(function (value){
 // ....
}).catch(function (err){
    // ....
})

3、Promise的方法

3.1 Promise.prototype.then()

then 方法是定義在原型對象 Promise.prototype 上,前面說過,它接收兩個可選參數,第一個參數是 fulfilled 狀態的回調函數,第二個參數是 rejected 狀態的回調函數。

then 方法返回的是一個新的 Promise 實例,方便我們采用鏈式寫法。比如 then 後面接著寫 then ,當第一個回調函數完成以後,會將返回結果作為參數,傳入第二個回調函數。這種鏈式方式可以很方便的指定一組按照次序調用的回調函數。

loadData().then(function (value){
    return 3
}).then(function (num){
    console.log("ok", num) // 3
})

3.2 Promise.prototype.catch()

catch 方法是用於指定發生錯誤時的回調函數。如果異步操作拋出錯誤,狀態就會變為 rejected ,就會調用 catch() 方法指定的回調函數,處理這個錯誤。

const promise = new Promise(function(resolve, reject) {
  throw new Error('unkonw error'); // 拋出錯誤狀態變為 -> reject
});
const promise = new Promise(function(resolve, reject) {
  reject('unkonw error') // 使用reject()方法將狀態變為 -> reject
});
promise.catch(function(error) {
  console.log(error);
});


Promise 對象的錯誤會一直向後傳遞,直到被捕獲為止。比如下面代碼: catch 會捕獲 loadData 和兩個 then 裡面拋出的錯誤。

loadData().then(function(value) {
  return loadData(value);
}).then(function(users) {
 
}).catch(function(err) {
  // 處理前面三個Promise產生的錯誤
});


如果我們不設置 catch() ,當遇到錯誤時 Promise 不會將錯誤拋出外面,也就是不會影響外部代碼執行。

const promise = new Promise(function(resolve, reject) {
   resolve(a) // ReferenceError: a is not defined
});
promise.then(function(value) {
  console.log('value is ', value)
});
setTimeout(() => { console.log('code is run') }, 1000); // code is run

3.3 Promise.prototype.finally()

finally() 方法不管 Promise 對象最後狀態如何,都會執行的操作。下面是我們使用 Promise 的一個常規結構。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

3.4 Promise.all()

Promise.all() 方法可以將多個 Promise 實例包裝成一個新的 Promise 實例返回。

const promise = Promise.all([p1, p2, p3]);


promise 狀態來依賴於“傳入的 promise ”。

  • 隻有當所有“傳入的 promise ”狀態都變成 fulfilled ,它的狀態才會變成 fulfilled ,此時“傳入的 promise ”返回值組成一個數組,傳遞給 promise 的回調函數。
  • 如果“傳入的 promise ”之中有一個被 rejected ,新 promise 的狀態就會變成 rejected ,此時第一個被 reject promise 的返回值,會傳遞給 promise 的回調函數。
const promises = [1,2,3,4].map(function (id) {
  return loadData(id);
});

Promise.all(promises).then(function (users) {
  // ...
}).catch(function(err){
  // ...
});

3.5 Promise.race()

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

Promise.race() 方法的參數與 Promise.all() 方法一樣。

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


Promise.all() Promise.race() 對比:

Promise.all() ,如果所有都執行成功則返回所有成功的 promise 值,如果有失敗則返回第一個失敗的值。
Promise.race() ,返回第一個執行完成的 promise 值,它可能是fulfilledrejected狀態。
這兩個方法的使用場景

場景一:用戶登錄社交網站主頁後,會同時異步請求拉取用戶信息,關註列表,粉絲列表,我們需要保證所有數據請求成功再進行渲染頁面,隻要有一個數據不成功就會重定向頁面,這裡可以使用 Promise.all

function initUserHome() {
  Promise.all([
  new Promise(getMe),
  new Promise(getFollows),
  new Promise(getFans)
])  
    .then(function(data){
     // 顯示頁面
  })
    .catch(function(err){
    // .... 重定向頁面
  });
};

initUserHome();

場景二:假如我們在做一個搶票軟件,雖然請求瞭很多賣票渠道,每次隻需返回第一個執行完成的 Promise ,這裡可以使用 Promise.race

function getTicket() {
  Promise.race([
  new Promise(postASell),
  new Promise(postBSell),
  new Promise(postCSell)
])  
    .then(function(data){
     // 搶票成功
  })
    .catch(function(err){
    // .... 搶票失敗,重試
  });
};

getTicket();

3.6 Promise.allSettled()

使用 Promise.all() 時,如果有一個 Promise 失敗後,其它 Promise 不會停止執行。

const requests = [
  fetch('/url1'),
  fetch('/url2'),
  fetch('/url3'),
];

try {
  await Promise.all(requests);
  console.log('所有請求都成功。');
} catch {
  console.log('有一個請求失敗,其他請求可能還沒結束。');
}

有的時候,我們希望等到一組異步操作都結束瞭,再進行下一步操作。這時就需要使用 Promise.allSettled() ,的它參數是一個數組,數組的每個成員都是一個 Promise 對象,返回一個新的 Promise 對象。它隻有等到參數數組的所有 Promise 對象都發生狀態變更(不管是 fulfilled 還是 rejected ),返回的 Promise 對象才會發生狀態變更。

const requests = [
  fetch('/url1'),
  fetch('/url2'),
  fetch('/url3'),
];

await Promise.allSettled(requests);
console.log('所有請求完成後(包括成功失敗)執行');

3.7 Promise.any()

隻要傳入的 Promise 有一個變成 fulfilled 狀態,新的 Promise 就會變成 fulfilled 狀態;如果所有傳入的 Promise 都變成 rejected 狀態,新的 Promise 就會變成 rejected 狀態。

Promise.any() Promise.race() 差不多,區別在於 Promise.any() 不會因為某個 Promise 變成 rejected 狀態而結束,必須等到所有參數 Promise 變成 rejected 狀態才會結束。

回到 Promise.race() 多渠道搶票的場景,如果我們需要保證要麼有一個渠道搶票成功,要麼全部渠道都失敗,使用 Promise.any() 就顯得更合適。

function getTicket() {
  Promise.any([
  new Promise(postASell),
  new Promise(postBSell),
  new Promise(postCSell)
])  
    .then(function(data){
     // 有一個搶票成功
  })
    .catch(function(err){
    // .... 所有渠道都失敗瞭
  });
};

getTicket();

3.8 Promise.resolve()

Promise.resolve() 方法將現有對象轉換為 Promise 對象。等價於下面代碼:

new Promise(resolve => resolve(1))


傳入的參數不同,處理

  • 參數 Promise 實例,它將不做任何修改、原封不動地返回這個實例。
  • 參數 thenable 對象,它會將這個對象轉為 Promise 對象,然後就立即執行 thenable 對象的 then() 方法。
let thenable = {
  then: function(resolve, reject) {
    resolve(1);
  }
};


  • 參數是普通值,返回一個新的 Promise 對象,狀態為 resolved
const promise = Promise.resolve(1);

promise.then(function (value) {
  console.log(value) // 1
});

  • 無參數,直接返回一個 resolved 狀態的 Promise 對象。

3.9 Promise.reject()

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

const promise = Promise.reject('unkonw error');
// 相當於
const promise = new Promise((resolve, reject) => reject('unkonw error'))

promise.then(null, function (s) {
  console.log(s)
});
// unkonw error

4、簡單場景

異步加載圖片:

function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
   const image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = url;
  });
}

請求超時處理:

//請求
function request(){
    return new Promise(function(resolve, reject){
       // code ....
         resolve('request ok')
    })
}

function timeoutHandle(time){
   return new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('timeout');
        }, time);
    });
}

Promise.race([
    request(),
    timeoutHandle(5000)
])
.then(res=>{
    console.log(res)
}).catch(err=>{
    console.log(err)// timeout
})

到此這篇關於前端JavaScriptPromise的文章就介紹到這瞭,更多相關JavaScript Promise內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: