- 撤销(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 是否拥有类似的设计,或者对于这类业务场景有更好的处理方式?
user2 • • 目录导航
我一般把整个流程写成一个 task ,并提供 cancel 函数,需要取消时就主动调用 task.cancel() |
user3 • • 目录导航
js 现在还没有自动取消异步任务的方法。可以用 AbortSignal ,但要每次 await 前 throwIfAbort 。或实现一个类似效果的东西。 |
user1 • • 目录导航
const sleep = (time) => { |
user2 • • 目录导航
不要用时间戳,可以用 symbol 来标记状态。Symbol()创建的每个 symbol 都是唯一的。 |
user3 • • 目录导航
@rabbbit 这个方式我之前实现过,但是存在麻烦的地方是:业务场景需要外部和内部都拥有 cancel 的能力,结果就是需要暴露一个额外的方法同时内部持有这个 reject ,担心会出现因为持有 reject 导致 promise 无法从内存中释放的情况 |
user1 • • 目录导航
@chenliangngng |
user2 • • 目录导航
我在业务中也遇到了这种问题,有一连串的异步调用链需要取消(可能执行到任意一步,不需要撤销,只要停止执行后续就行),没啥头绪。 |
user3 • • 目录导航
脑洞,不推荐这么写 |
user1 • • 目录导航
|
user2 • • 目录导航
rxjs switchMap ? |
user3 • • 目录导航
用 RxJS 可以方便地实现,跟 AI 交流了一会后给出了如下代码: |
user1 • • 目录导航
给输出结果加个时间线: |
user2 • • 目录导航
@rabbbit 用生成器确实是一个好办法,在可被取消的时间点添加 yield 。暂停后只要不调用 next() 方法就不会继续执行,这样就需要自己写一个执行器,调度的时机可以自己灵活控制,像 co 那种执行器是自动执行的,async 函数也是自动执行的。 |
user3 • • 目录导航
我用生成器简单实现了一下,代码 https://pastebin.com/hi04KbPd |
user1 • • 目录导航
这好像是个面试题,串行异步任务链的中止与继续,你看是这个么 |
user2 • • 目录导航
https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal |
user3 • • 目录导航
感觉你逻辑挺合理的,好像只能写个工具方法重构一下 |
user1 • • 目录导航
做一个任务队列,输入一次,移除上一次剩余的没有完成的任务,再往队列里面加一组异步的任务, 异步的任务处理从任务队列里面取。 |
user2 • • 目录导航
@pursuer |
为什么要用时间戳,直接存储状态不就可以了
不论是 promise 和 ajax 都可以取消