- 撤销(Ctrl+Z)
- 重做(Ctrl+Y)
- 清空
- H 标题(Ctrl+1~6)
- 一级标题
- 二级标题
- 三级标题
- 四级标题
- 五级标题
- 六级标题
- 插入提示
- 提示
- 注意
- 警告
- 详细信息
- 粗体(Ctrl+B)
- 斜体(Ctrl+I)
- 删除线
- 插入引用(Ctrl+Q)
- 无序列表(Ctrl+U)
- 有序列表(Ctrl+O)
- 表格
- 插入分割线
- 插入链接(Ctrl+L)
- 插入图片
- 添加图片链接
- 插入代码块
- 关闭同步滚动
- 全屏(按ESC还原)
- 开启预览
<div class="markdown_body"><p>最近遇到的业务场景:</p> <ol> <li>用户输入时进行初始化,初始化过程中包含了异步任务的串行</li> <li>初始化过程中,不阻塞用户输入</li> </ol> <p>如果用户输入时上一次的初始化还没结束,就会出现多次初始化的并行。我目前的处理方式是初始化开始时记下当前状态,每结束一个串行的异步任务,都进行一次状态比对。简化后的代码如下:</p> <pre><code class="language-javascript">/** * @type {number | undefined} */ let state = undefined /** * @param {number} ms * @returns {Promise<void>} * @description sleep for ms milliseconds to simulate async task */ function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)) } /** * @returns {Promise<void>} * @description init async task */ async function init() { const current = Date.now() state = current await sleep(500) if (state !== current) { console.warn(`state ${current} init: canceled`) return } await sleep(500) if (state !== current) { console.warn(`state ${current} init: canceled`) return } console.log(`state ${current} init: done`) state = undefined } </code></pre> <p>但我感觉这种做法不太合理,虽然上面用当前时间戳模拟了状态,但实际上业务场景中可能会出现某几次用户输入一致,所以状态一致,进而导致这几次输入执行的初始化过程无法正常中断。像 C#中有 <code>CancellationToken</code> 可以直接调用 <code>Cancel</code> 取消异步任务。不知道 JS 是否拥有类似的设计,或者对于这类业务场景有更好的处理方式?</p> </div>
最近遇到的业务场景:
- 用户输入时进行初始化,初始化过程中包含了异步任务的串行
- 初始化过程中,不阻塞用户输入
如果用户输入时上一次的初始化还没结束,就会出现多次初始化的并行。我目前的处理方式是初始化开始时记下当前状态,每结束一个串行的异步任务,都进行一次状态比对。简化后的代码如下:
/**
* @type {number | undefined}
*/
let state = undefined
/**
- @param {number} ms
- @returns {Promise<void>}
- @description sleep for ms milliseconds to simulate async task
*/
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
/**
- @returns {Promise<void>}
- @description init async task
*/
async function init() {
const current = Date.now()
state = current
await sleep(500)
if (state !== current) {
console.warn(state ${current} init: canceled)
return
}
await sleep(500)
if (state !== current) {
console.warn(state ${current} init: canceled)
return
}
console.log(state ${current} init: done)
state = undefined
}
但我感觉这种做法不太合理,虽然上面用当前时间戳模拟了状态,但实际上业务场景中可能会出现某几次用户输入一致,所以状态一致,进而导致这几次输入执行的初始化过程无法正常中断。像 C#中有 CancellationToken 可以直接调用 Cancel 取消异步任务。不知道 JS 是否拥有类似的设计,或者对于这类业务场景有更好的处理方式?
user1 • • 目录导航
也可以简单的写个装饰函数,把所有异步任务的函数都装饰一遍再调用 |
user2 • • 目录导航
没太理解业务场景,这不应该是给用户输入的事件加 debounce 的事吗 |
user3 • • 目录导航
只要是异步,毫无疑问 rxjs 就是更好的处理方式 |
user1 • • 目录导航
我是用 redux-saga ,fork 一个异步 task 任务,然后 take 一个停止 action ,cancel 掉这个 task 任务就好 |
user2 • • 目录导航
把所有的串行异步用 promise.then 链式连接起来。对外暴露 promise,每次用户有新输入调用 promise.reject 终止异步任务链。然后生成新的 |
user3 • • 目录导航
@yigefanqie 不一样,他是想取消并行的初始化,防抖不会取消函数的执行。比如连续输入 abc 后 abc 三次输入被防抖了,开始初始化,但这时用户再输入 d ,会进行第二次初始化,他想第二次初始化时如果第一次还在初始化则中止第一次的初始化。 |
user1 • • 目录导航
我理解的这样:用户输入时的异步串行任务是不是都是无副作用的,如果是无作用的,直接丢弃之前的初始化,重新初始化就行了,配合上 debounce 降低下初始化触发频率。丢弃掉的初始化如果都是 promise 不用担心内存问题,会自动回收的。 |
user2 • • 目录导航
|
user3 • • 目录导航
@yigefanqie #28 一般初始化都是资源占用型过程,最佳实践还是直接中断。 |
user1 • • 目录导航
@tsanie |
user2 • • 目录导航
|
user3 • • 目录导航
用单独的线程执行任务,需要取消的时候直接杀线程,简单粗暴又有效 |
@jones2000 是的,其实跟上面使用 generator 的思路一致