第一位男子叫异步 身着西装赛徐公 他有个弟弟叫回调 但异步只爱Promise
第二位男子叫节流 是个浪漫少年 喜欢去救助加班的程序媛但更喜欢跳舞
直到有那么一天 他们决定出去闯荡世界 他们踏上了旅程 成为了人们的偶像 偶然有一天 在HTML的表单里 有这样一种场景:
<form id="form">
<!-- <label for="name">Name:</label>
<input type="text" name="name" id="name"> -->
<button type="submit">submit</button>
</form>
点击“提交”就往服务端发送一个请求:
// 网络请求
function api(data) {
console.log('submiting', data)
return fetch("https://httpbin.org/delay/1.5", {
body: JSON.stringify(data),
method: "POST",
mode: "cors"
});
}
const form = document.getElementById('form')
const handler = async function(e) {
e.preventDefault()
const rez = await someApi({
msg: 'some data to be sent'
})
console.log(rez)
}
form.addEventListener(
'submit',
handler
)
为防止用户重复提交,我们通常会维护一个loading
状态...但是写得多了,难免有一种机械劳动的感觉。而且,当一个表单出现很多按钮时,我岂不是维护很多loading
变量? 我看着眼睛好累,而且接口响应很快,偷偷少写一个loading
应该不会被发现吧🌚,可是万一接口要是挂了...算了,来不及想这些了
上面的场景不知道你有没有经历过呢?下面我们就来探究一下:
能不能站着就把钱挣咯?
我们先来梳理一下:
- 短时间内每个事件都会产生一个promise,核心需求是事件去重
- promise的响应时间是不确定的
第一点,去重,先回想一下同步代码中事件去重:节流(throttle)、防抖(debounce)。关于这两者,相信你已经很熟悉了,我们一句话概括:二者都是在单位时间内的多次相同事件中取一次调用,不同的是前者取的第一次,后者取的最后一次。把我们的需求也改成这种句式:在短时间内的多次相同事件中取一次调用。所以,这个“短时间内”才是关键 !
第二点,promise的响应时间是不确定的,我们希望上一个promise结束之前,接下来的事件统统丢弃。所以,“短时间内”就等于“上一个promise的pending期间”,“接下来的事件统统丢弃”意思就是“取第一次”,所以我们的需求就是:在上一个promise的pending期间的多次相同事件中取第一次(就是这个正在pending的promise)调用。
思路都参考了,代码也参考一下吧,这里贴个简易版的节流:
/**
* @description 节流
* @param {function} fn
* @param {number} ms 毫秒
* @returns {function} 节流后的function
*/
function throttle (fn, ms = 300) {
let lastInvoke = 0;
return function throttled(...args) {
const now = Date.now();
if (now - lastInvoke < ms) return;
lastInvoke = now;
fn.call(this, ...args);
};
};
依葫芦画瓢:
/**
* @description 异步节流:上一次的promise pending期间,不会再次触发
* @param {() => Promise<any>} fn
* @returns {() => Promise<any>} 节流后的function
*/
function throttleAsync(fn) {
let isPending = false;
return function(...args) {
if (isPending) return new Promise(() => {});
isPending = true;
return fn
.call(this, ...args)
.then((...args1) => {
isPending = false;
return Promise.resolve(...args1);
})
.catch((...args2) => {
isPending = false;
return Promise.reject(...args2);
});
};
}
使用方法(Demo):
// 网络请求
function api(data) {
console.log('submiting', data)
return fetch("https://httpbin.org/delay/1.5", {
body: JSON.stringify(data),
method: "POST",
mode: "cors"
});
}
const throttledApi = throttleAsync(api)
// 模拟业务逻辑
const button = document.getElementById('button')
const handler = async function() {
const rez = await throttledApi({
msg: 'some data to be sent'
})
console.log('completed')
}
button.addEventListener(
'click',
handler
)
打开开发者工具可以看到,无论点击多快,始终不会出现请求并行的情况:
大功告成!
同理debounceAsync
(Demo):
/**
* @description 异步去抖:短时间内触发多次,取最后一次触发的结果
* @param {() => Promise<any>} fn
* @returns {() => Promise<any>} 去抖后的function
*/
function debounceAsync(fn) {
let lastFetchId = 0;
return function(...args) {
const fetchId = ++lastFetchId;
return fn
.call(this, ...args)
.then((...a1) => {
if (fetchId !== lastFetchId) {
return new Promise(() => {});
} else {
return Promise.resolve(...a1);
}
})
.catch((...a2) => {
return Promise.reject(...a2);
});
};
}
“偷懒”是程序员第一生产力,学到了吗🤔?
原文链接:blog.bowen.cool/zh/posts/wh…
欢迎我的公众号,佛性更新:
2021.05.31 更新:
所有代码已经包含到 github.com/bowencool/a… 仓库中,并且已发布到 npm