【面試高頻知識點】promise的理解(通俗易懂)

前一段時間找工做,面試了大大小小十幾家公司,其中也包含了騰訊、blue等知名公司。總結面試經歷,發現本身還有不少不足的地方,許多知識點都知其然不知其因此然。趁着最近事比較少,會陸陸續續總結一些面試的高頻知識點,提高本身知識的深度和廣度。本文是系列文章之一:promise的理解。javascript

1、何爲promise,咱們爲什麼要使用它?

因爲JavaScript語言特性,全部程序都是單線程執行的。因爲這個特性,JavaScript的一些瀏覽器事件、請求事件都是異步執行的,經過回調函數處理異步的結果。這是很常見的語法,可是在一些場景下,就會造成回調函數嵌套回調函數,有的狀況甚至套用多層,造成了「回調地獄」,這樣使得代碼臃腫可讀性差並且難以維護。php

<!--一個地獄回調的例子,上一個函數的回調結果被下一個函數所依賴-->
const verifyUser = function(username, password, callback){
   require.verifyUser(username, password, (error, userInfo) => {
       if (error) {
           callback(error)
       }else{
           require.getRoles(userInfo, (error, roles) => {
               if (error){
                   callback(error)
               }else {
                   require.logAccess(roles, (error) => {
                       if (error){
                           callback(error);
                       }else{
                           callback(null, userInfo, roles);
                       }
                   })
               }
           })
       }
   })
}
複製代碼

爲了解決這種問題,社區提出了一些解決方案,採用鏈式調用的方法,來解決異步回調,並在在ES6被統一成規範。能夠說Promise 是異步編程的一種解決方案。前端

2、Promise的基本用法

基本用法

做爲新的規範,promise採用更加直觀也更加易讀的方式解決回調嵌套。ES6規定,promise對象是一個構造函數,經過new關鍵字來生成實例。下面是promise的基本用法java

<!--promise的基本用法-->
 const promise = new Promise((resolve, reject) => {
  // 異步操做的代碼
  if (success){
    resolve(value);
  } else {
    reject(error);
  }
});
複製代碼

Promise構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolve和reject,他們是JavaScript引擎提供的兩個函數。異步操做有兩種結果:成功或失敗 ios

promise內部狀態轉變

  • resolve函數在異步操做由pending狀態(執行進行中)變爲resolved(成功狀態)時觸發,傳遞操做成功後的結果;
  • reject函數在異步操做從pending狀態(執行進行中)變爲rejected(失敗狀態)時觸發,傳遞操做失敗後的結果。

注意promsie狀態 只能由 pending => fulfilled/rejected, 一旦修改就不能再變程序員

promise對象方法

Promise.prototype.then()

那麼問題來了,剛纔咱們說到promise狀態改變後出觸發相應的函數,那麼咱們處理狀態改變的代碼要寫在哪裏呢? 沒錯,就是then()方法。then方法是定義在原型對象Promise.prototype上的,它的做用是爲 Promise 實例添加狀態改變時的回調函數then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。面試

<!--promise then方法-->
 const promise = new Promise((resolve, reject) => {
   resolve('fulfilled'); // 狀態由 pending => fulfilled
 }).then(result => { 
    console.log(result); // 'fulfilled' 
 }, reason => { 
    
 })
複製代碼
const promise = new Promise((resolve, reject) => {
  reject('rejected '); // 狀態由 pending => rejected
}).then(result => { 
    
}, reason => { 
   console.log(reason); // 'rejected'
})
複製代碼

上邊說過,promise狀態一旦修改就不能再變 只能由 pending => fulfilled或者 pending => rejected編程

promise採用鏈式調用,then()爲 Promise 註冊回調函數,參數爲上一個任務的返回結果,因此鏈式調用裏then 中的函數必定要 return 一個結果或者一個新的 Promise 對象,纔可讓以後的then 回調接收。axios

const promise = new Promise((resolve, reject) => {
     resolve("success")
 })
.then(result => {
    return result
})
.then(result => {
    console.log(result)  // "success"
})
.catch(err => {})
複製代碼

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的別名,也就是異步操做發生錯誤時的回調函數,另外,then()方法裏的回調函數發生錯誤也會被catch()捕獲。api

<!--promise catch方法-->
 const promise = new Promise((resolve, reject) => {
   throw new Error('err');
   // 或者reject(new Error('err'));
 }).then(result => { 
    console.log(result); 
 }).catch(err => {
    // 處理前兩個promise產生的錯誤
     console.log(err)
 })
複製代碼

到這裏,細心的同窗會發現,既然我then()方法第二個參數能夠用來拋出錯誤,幹嗎還要用這個catch()方法。 其實仍是有區別的,在鏈式操做裏,任何promise拋出的同步或異步錯誤均可以被then()方法捕獲,而reject則處理當前promise的錯誤。所以,建議不要在then方法裏面定義 reject 狀態的回調函數(即then的第二個參數),老是使用catch方法,這樣也更接近同步的寫法(try/catch)。

<!--promise catch方法-->
const promise = new Promise((resolve, reject) => { 
    // some code
})
// good
promise.then(result => { 
   // success
}).catch(err => {
    // err
})
// not recommend
promise.then(result => {
    //success
},err => {
    //err
});
複製代碼

Promise.prototype.finally()

finally()方法是在ES2018引入標準的,該方法表示promise不管什麼狀態,在執行完then()或者catch()方法後,最後都會執行finally()方法。

<!-- promise finally方法-->
const promise = new Promise((resolve, reject) => {})
.then(result => {})
.catch(err => {})
.finally(() => {})
複製代碼

Promise.all()

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

<!-- promise.all方法-->
const promise1 = new Promise((resolve, reject) => {resolve("promise1")}),
     promise2 = new Promise((resolve, reject) => {resolve("promise2")}),
     promise3 = new Promise((resolve, reject) => {resolve("promise3")});
     
Promise.all([promise1,promise2,promise3]).then(data => { 
  console.log(data); 
  // ["promise1", "promise2", "promise3"] 結果順序和promise實例數組順序是一致的
}).catch(err => {
  consolo.log(err)
});
複製代碼

只有promise一、promise二、promise3的狀態都變成fulfilled,Promise.all的狀態纔會變成fulfilled,此時promise一、promise二、promise3的返回值組成一個數組,傳遞給Promise.all的回調函數。

只要promise一、promise二、promise3之中有一個被rejected,Promise.all的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給Promise.all的回調函數

在作項目的時候咱們常常會碰到一個頁面要有多個請求,咱們可使用promise.all封裝,便於請求管理。

相似的axios也有axios.all()方法處理併發請求

Promise.race()

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

<!-- promise.race方法-->
const promise = Promise.race([promise1, promise2, promise3]);
複製代碼

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

3、Promise的應用

與axios結合

項目中咱們常常會遇到須要根據業務將axios再封裝的,好比請求攔截設置token以及Content-Type,響應攔截根據不一樣的狀態碼設置不一樣的響應。此外咱們還能夠將axios再封裝

import axios from "./axios"
import qs from "qs";
export default {
    get: function(url, params) {
      return new Promise((resolve, reject) => {
        axios.get(url, {params: params})
          .then(res => {
            resolve(res)
          })
          .catch(err => {
           console.log(err)
          })
      })
    },
    post: function(url, params) {
      return new Promise((resolve, reject) => {
        axios.post(url, qs.stringify(params))
          .then(res => {
            resolve(res);
          })
          .catch(err => {
              console.log(err)
          })
      });
    }
}
<!--使用 整個user模塊的請求都在此文件管理-->
import require from "@/utils/require"
const user = {
    userList() {
      return require.post("/api.php", {}).then(res => res.result)
    },
    userInfo() {
      return require.post("/api.php?&uid=20", {}).then(res => res.result)
    },
    ...
}
export default user
複製代碼

異步加載圖片

用promise實現異步加載圖片的例子

function loadImageAsync(url) {
    return new Promise((resolve, reject) => {
      const image = new Image();
      image.onload = () => {
        resolve(image);
      };
      image.onerror = () => {
        reject(new Error('Could not load image at ' + url));
      };
      image.src = url;
    });
}
const loadImage = loadImageAsync(url);
複製代碼

寫在最後

前端技術近年來發展迅速,新技術層出不窮,做爲一個程序員er也要有持續學習的覺悟。 歡迎你們關注個人公衆號:前端Readhub 。😄😄

相關文章
相關標籤/搜索