前一段時間找工做,面試了大大小小十幾家公司,其中也包含了騰訊、blue等知名公司。總結面試經歷,發現本身還有不少不足的地方,許多知識點都知其然不知其因此然。趁着最近事比較少,會陸陸續續總結一些面試的高頻知識點,提高本身知識的深度和廣度。本文是系列文章之一:promise的理解。javascript
因爲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 是異步編程的一種解決方案。前端
做爲新的規範,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
注意promsie狀態 只能由 pending => fulfilled/rejected, 一旦修改就不能再變程序員
那麼問題來了,剛纔咱們說到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方法是.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
});
複製代碼
finally()方法是在ES2018引入標準的,該方法表示promise不管什麼狀態,在執行完then()或者catch()方法後,最後都會執行finally()方法。
<!-- promise finally方法-->
const promise = new Promise((resolve, reject) => {})
.then(result => {})
.catch(err => {})
.finally(() => {})
複製代碼
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 實例,包裝成一個新的 Promise 實例。
<!-- promise.race方法-->
const promise = Promise.race([promise1, promise2, promise3]);
複製代碼
上面代碼中,只要promise一、promise二、promise3之中有一個實例率先改變狀態,promise的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
項目中咱們常常會遇到須要根據業務將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 。😄😄