为了账号安全,请及时绑定邮箱和手机立即绑定

使用异步数据存储 API 的 JavaScript 事件处理程序导致竞争条件

使用异步数据存储 API 的 JavaScript 事件处理程序导致竞争条件

慕姐4208626 2022-11-11 13:29:55
每次触发某些浏览器事件时(例如,当浏览器选项卡关闭时),我都需要更新一些数据:chrome.tabs.onRemoved.addListener(async (tabId) => {  let data = await getData(); // async operation  ...                         // modify data   await setData(data);        // async operation});问题是,当多个此类事件触发器快速连续时,异步getData()可能会在事件处理程序的后续调用中返回陈旧的结果,然后setData()才有机会在较早的事件中完成,从而导致结果不一致。如果事件处理程序可以同步执行,则不会发生此问题,但getData()两者setData()都是异步操作。这是比赛条件吗?处理此类逻辑的推荐模式是什么?- - 更新 - -为了提供更多上下文,getData()并且setData()只是一些 Chrome 存储 API 的承诺版本:async function getData() {  return new Promise(resolve => {    chrome.storage.local.get(key, function(data) => {      // callback    });  });}async function setData() {  return new Promise(resolve => {    chrome.storage.local.set({ key: value }, function() => {      // callback    });  });}出于可读性目的,我将 API 调用包装在 Promise 中,但我认为无论哪种方式都是异步操作?
查看完整描述

1 回答

?
函数式编程

TA贡献1807条经验 获得超9个赞

getData()对于具有异步 API 的数据存储,您有一个相当经典的竞争条件,如果您在数据处理中使用异步操作(在和之间),竞争条件会更糟setData()。异步操作允许另一个事件在中间运行你的处理,破坏了你的事件序列的原子性。


以下是如何将传入的 tabId 放入队列并确保您一次只处理其中一个事件的想法:


const queue = [];


chrome.tabs.onRemoved.addListener(async (newTabId) => {

    queue.push(newTabId);

    if (queue.length > 1) {

        // already in the middle of processing one of these events

        // just leave the id in the queue, it will get processed later

        return;

    }

    async function run() {

        // we will only ever have one of these "in-flight" at the same time

        try {

            let tabId = queue[0];

            let data = await getData(); // async operation


            ...                         // modify data 


            await setData(data);        // async operation

        } finally {

            queue.shift();              // remove this one from the queue

        }

    }

    while (queue.length) {

        try {

            await run();

        } catch(e) {

            console.log(e);

            // decide what to do if you get an error

        }

    }

});

这可以变得更通用,因此可以在多个地方(每个都有自己的队列)重复使用,如下所示:


function enqueue(fn) {

    const queue = [];

    return async function(...args) {

        queue.push(args);       // add to end of queue

        if (queue.length > 1) {

            // already processing an item in the queue,

            // leave this new one for later

            return;

        }

        async function run() {

            try {

                const nextArgs = queue[0];  // get oldest item from the queue

                await fn(...nextArgs);      // process this queued item

            } finally {

                queue.shift();              // remove the one we just processed from the queue

            }

        }

        // process all items in the queue

        while (queue.length) {

            try {

                await run();

            } catch(e) {

                console.log(e);

                // decide what to do if you get an error

            }

        }

    }

}


chrome.tabs.onRemoved.addListener(enqueue(async function(tabId) {

    let data = await getData(); // async operation


    ...                         // modify data


    await setData(data);        // async operation

}));


查看完整回答
反对 回复 2022-11-11
  • 1 回答
  • 0 关注
  • 134 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号