JS异步与同步
这里展示一个操作场景:需要对数据进行异步处理,但这次操作可能会失败,所以需要定期对数据进行再次处理,直至处理成功。
实现:手动触发的处理以及定期触发的处理,是相同的,即可以抽取出来成一个公共函数,定期操作使用setInterval实现,对应以下的示例代码:
function otherAsyncApiCall(n){ return new Promise(resolve => setTimeout(resolve, n * 1000)) } // 抽取出公共的数据操作函数 async function dataOperation(data){ console.log(data + "读取数据库,并操作"); // 模拟数据处理中的异步操作 await otherAsyncApiCall(3); console.log(data + "操作结果写入数据库"); } async function run(){ // 开启定期操作,每5秒操作一次 setInterval(()=>{ dataOperation("定期触发 -"); },5000); await dataOperation("1手动触发 -"); await dataOperation("2手动触发 -"); } run();
执行结果:
1手动触发 -读取数据库,并操作 1手动触发 -操作结果写入数据库 2手动触发 -读取数据库,并操作 定期触发 -读取数据库,并操作 2手动触发 -操作结果写入数据库 定期触发 -操作结果写入数据库
红色标记中出现一个严重问题:出现重复操作和写数据库。
解决办法是换一种实现思路:
维护一个全局的消息队列,添加一个额外的定时操作去定期消费队列中的消息,原有的定时操作以及手动触发都换成往队列中写数据,而不实际操作数据。这样一来,实际消费消息的地方只有一个,就能避免以上问题了。
简易版的消息队列实现:
+function(){ const queue = []; const handlerMap = {}; const gap = 5 * 1000; window.messageQueue = { put(type, data){ queue.push({type, data}) }, get(){ return queue.shift(); }, // 要求传入的fn必须返回一个Promise,告知执行的完成情况 handle(type, fn){ handlerMap[type] = fn; } }; window.messageQueue.MSG_TYPE = { UPLOAD_IMG: 1, }; async function eatMsg(){ const target = queue.shift(); if(!target){ return; } const handlerFn = handlerMap[target.type]; return handlerFn && handlerFn(target.data); } function range(){ const next = ()=> setTimeout(range, gap); // 保证循环不会断 eatMsg().then(next).catch(next); } range(); }();
ps:setInterval的循环中,不会受到await的影响而延迟,如以下代码中数据操作还没处理完,就会马上开始下一次触发,所以需要保证两次触发的时间间隔要大于实际的数据处理时间,才是安全的。
setInterval(async ()=>{ // 这里的异步操作是三秒 await dataOperation("定期触发 -"); },1000);