9012 年底,做爲一個前端,說不了解 Promise 對象用法的基本不存在,這裏就不對功能用法進行介紹了。但本文將會講述你可能不知道的 Promise 3 種奇妙用法。固然,每種用法都會有其適用的特殊場景。html
對於一個對象而言,可以被緩存並非一件難以理解的事情。緩存使用的意義每每是爲了解決性能問題。而對於一個特定請求的 Promise 對象而言,緩存的意義在於同時多個組件的使用該請求,會由於請求未返回而進行屢次請求。一圖勝千言,圖示以下:前端
由於在某些特定需求或者場景下(甚至由於團隊的因素),某個組件在能夠在頁面單獨使用,也能夠結合其餘組件共同使用。若此時多個組件都須要對某個通用數據進行請求,就會發生屢次請求,對性能不利。但若是所有移植到父組件去請求,又是須要一頓操做,對開發不爽。vue
因此這時候咱們基於 api 與 請求參數加緩存。先寫一個生成 key 的函數(此函數僅僅只適用簡單的請求參數,不適合對象等複雜數據結構,由於是通用型數據,不考慮太複雜的請求參數,若有需求能夠自行改造)。ios
// 生成key值錯誤
const generateKeyError = new Error("Can't generate key from name and argument")
// 根據當前的請求參數生成 key 值
function generateKey(name, argument) {
// 從arguments 中取得數據而後變爲數組
const params = Array.from(argument).join(',')
try{
// 返回 字符串,函數名 + 函數參數
return `${name}:${params}`
}catch(_) {
// 返回生成key錯誤
return generateKeyError
}
}
複製代碼
下面是數據請求緩存,不過使人以爲惋惜的是: 數據請求緩存並不能解決屢次請求的問題。git
const dataCache = new Map()
async getxxx(params1, params2) {
const key = generateKey('getxxx', [params1, params2])
// 從data 緩存中獲取 數據
let data = dataCache.get(key)
if (!data) {
// 沒有數據請求服務器
const res = await request.get('/xxx')
// 其餘操做
...
data = ...
// 設置數據緩存
dataCache.set(key, data)
}
return data
}
複製代碼
由於雖然 js 是單線程的,因此在第二個以及以上的組件請求時候,會由於請求未返回而進行再次請求 api。流程以下:github
若是緩存的是 Promise 對象,則該方案能夠解決問題。axios
const promiseCache = new Map()
async getxxx(params1, params2) {
const key = generateKey('getxxx', [params1, params2])
// promiseCache 緩存中獲取 緩存
let xxxPromise = promiseCache.get(key);
// 當前promise緩存中沒有 該promise
if (!xxxPromise) {
xxxPromise = request.get('/getxxx').then(res => {
// 對res 進行操做
...
}).catch(error => {
// 在請求回來後,若是出現問題,把promise從cache中刪除 以免第二次請求繼續出錯
promiseCache.delete(key)
return Promise.reject(error)
})
promiseCache.set(key, promise)
}
return xxxPromise
}
複製代碼
流程以下:小程序
同時,由於 promise 是異步操做,因此在發生錯誤時候 catch 中去除緩存以便於緩存了錯誤的promise。segmentfault
該方案能夠減輕同一時間屢次請求同一數據所帶來的性能問題。後端
若是你還想結合過時時間與裝飾器來對緩存進行賦能,能夠參考我以前的博客文章 前端 api 請求緩存方案
在寫關於異步請求時候,一般是基於請求直接返回 api 請求響應數據,對其進行正常和錯誤處理。當時屢次異步操做從而返回正確與錯誤的流程卻不多進行梳理。若是在一次請求內有多個異步操做:代碼就會變得難以維護。
學習 Promise 時候,每每會與有限狀態機結合在一塊兒說,若是你實現過 Promise,你就清晰的知道: 若是內部沒有狀態沒有發生變化,能夠執行大量異步操做。體現爲若是沒有調用 resolve 或者 reject 函數,則不會對於當前 Promise 的狀態和值進行修改,也就不會執行後面的鏈式調用。
// 異步操做封裝
function asyncOpt(opt: any) {
return new Promise((resolve, reject) => {
// 傳入的 opt 異步操做
// 如 請求失敗,失敗的邏輯判斷後再次請求
// 又如多個 異步操做, 在最後一個異步操做成功後執行
reslove(result)
// 多個 異步操做中的 catch, 在每一個錯誤中執行
reject(error)
})
}
asyncOpt(data).then(result => {
// 正常流程
}).catch(error => {
// 錯誤流程
})
複製代碼
寫出如上的代碼,就能夠在不少業務項內進行操做,諸如某些操做有前置權限請求,或者某些錯誤代碼須要從新請求或者埋點等操做。
若是以爲上述的例子不夠複雜,不夠體現出 Promise 封裝的妙用,你能夠研究關於微信登錄態的管理。事實上,在沒有知道這種用法以前,確實沒有很好的辦法解決這種問題。
固然,github 上已經有了開源實踐 weRequest,該庫實現了無感知登錄,且代碼風格與結構很是值得學習,能夠參考我以前的博客文章 從 WeRequest 登錄態管理來聊聊業務代碼。
同時,能夠封裝異步操做可並不只僅只是指代異步請求,若是是你使用過Element confirm,必定對以下代碼不陌生。
this.$confirm('此操做將永久刪除該文件, 是否繼續?', '提示', {
confirmButtonText: '肯定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$message({
type: 'success',
message: '刪除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消刪除'
});
});
複製代碼
這樣的話,不須要再界面上寫 confirm 以及一些控制顯隱的代碼,基於配置(字符串) 觸發 promise 開始顯示後銷燬。
若是你完整引入了 Element,它會爲 Vue.prototype 添加以下全局方法:
alert,
prompt。所以在 Vue instance 中能夠採用本頁面中的方式調用
MessageBox
。調用參數爲:
$msgbox(options)
$alert(message, title, options)
或 $alert(message, options)
$confirm(message, title, options)
或 $confirm(message, options)
$prompt(message, title, options)
或 $prompt(message, options)
最後結合全局方法和 渲染函數 甚至也能夠實現 Modal 配置化,傳入組件,配置以及數據。能夠相似於以下寫法(固然,事實上用不用 Promise 均可以實現該方案,只不過 Promise 的狀態轉化很適合,與其本身實現一個狀態機,倒不如使用promise):
this.$modal(xxxComponent, componentConfig, propConfig).then(result => {
// 根據不一樣返回結果來處理
}).catch(reason => {
// 取消處理方法
})
// 甚至還能夠加 finally 方法
複製代碼
最近有小夥伴來找我詢問,如何解決後一個請求比前一個請求還要快,由於他寫了輸入實時查詢的功能。我直接讓他使用防抖函數,可是他告訴我他已經使用了 500ms 的防抖可是服務端仍舊是會存在問題。
原本考慮再前一個請求成功後再進行下一次,可是考慮到這個方案會慢點很明顯,後面考慮請求惟一化,可是由於使用 axios 作請求庫,該請求並不特殊,特殊化處理明顯是增長了代碼複雜度,也是不太好。
後面他告訴我,他已經解決了此問題,由於 axios 有一個方法能夠取消請求。也就是若是他進行下一個請求,便會取消上一個請求。下面代碼是官方示例:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
複製代碼
其實我是知道 Promise 中是有 cancelble 提案,可是該提案在第一階段就由於被谷歌的強烈反對而取消了,那麼我就去看 axios 源碼來看一看如何實現取消。下面代碼在 xhr.js 中。
// 若是配置出現 cancelToken
if (config.cancelToken) {
// Handle cancellation
// 設定 處理取消方法
config.cancelToken.promise.then(function onCanceled(cancel) {
// 請求被置空,直接返回,以免出錯
if (!request) {
return;
}
// xhr.abort 取消請求
request.abort();
// 執行 reject
reject(cancel);
// 請求置空
request = null;
});
}
複製代碼
先談談 abort 函數,abort 是 xhr 對象中的方法,根據 mdn :
若是該請求已被髮出,XMLHttpRequest.abort() 方法將終止該請求。當一個請求被終止,它的 readyState 屬性將被置爲0(
UNSENT
)。
這個請求指的是 http 請求,這樣就會出現一個問題,基於http請求原理,當一個請求從客戶端發出去以後,服務器端收到請求後,一個請求過程就結束了,這時就算是客戶端abort這個請求,服務器端仍會作出完整的響應,只是這個響應客戶端不會接收。因此實質上,後端仍是處理了請求,可是前端不對該方法進行處理。
其中 promise 取消 核心代碼以下 CancelToken.js。
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
// 設定 resolvePromise
var resolvePromise;
// xhr config.cancelToken.promise.then 就是當前的 promise
this.promise = new Promise(function promiseExecutor(resolve) {
// 設定 和導出 resolve
resolvePromise = resolve;
});
var token = this;
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
// 執行 resolvePromise
resolvePromise(token.reason);
});
}
/** * 對應使用中 source * axios.get('/user/12345', { * cancelToken: source.token * }) * source.cancel('Operation canceled by the user.'); */
CancelToken.source = function source() {
var cancel;
// 返回 cancel token 對象
var token = new CancelToken(function executor(c) {
// 利用 excutor 來把取消函數導出來。也就是 CancelToken excutor函數
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
複製代碼
能夠看到 axios 的代碼關係仍是有必定的複雜度。固然也是由於當前 Promise 沒有辦法像 setTimeout 等一些方法在調用時候直接返回取消函數,因此不得不借助另外一個 promise 異步來處理。同時,也是把複雜度留給了本身,因此仍是須要多讀幾遍。 調用關係以下。
CancelToken 設定了取消的 promise 調用關係, xhr 在有請求配置 cancelToken 的狀況下,將當前請求注入的 cancelToken 中的 then 結合,使得調用了 cancel 後能夠直接改變 xhr 內部狀態。
固然在頻繁的頁面跳轉,同時還有定時請求時候,跳轉中的數據請求實際上意義不大: 能夠參考 vue axios請求 取消上一個頁面全部請求 批量取消請求
取消請求問題其實較爲小衆的,大部分是能夠從請求源頭來解決的,同時也由於對於服務端的處理並無減輕,因此事實上不處理其實倒也沒什麼問題。可是其中也遇到太小程序中有一些全局的服務,在請求完成後因爲觸發不到頁面數據而報錯的問題。
雖然是小衆問題,可是遇到該特定場景須要提供解決方案。
同時對於上面的異步操做組件來講,泄露出 resolve 和 reject 函數以便於直接執行 then 或者 catch 是否有意義,又或者中途改變異步組件的實現流程是否真的對業務有所幫助也是值得思考的。
若是你以爲這篇文章不錯,但願能夠給與我一些鼓勵,在個人 github 博客下幫忙 star 一下。 博客地址