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);

 

posted @ 2019-11-14 15:04  HelloHello233  阅读(243)  评论(0编辑  收藏  举报