按哪个顺序接收storage
个事件?
例如,TAB_1执行代码
localStorage.set('key', 1);
localStorage.set('key', 2);
Tab_2执行代码
localStorage.set('key', 3);
localStorage.set('key', 4);
它们同时执行代码.
问题:
- %2可以存储在%1之前吗?
- 3或4可以存储在1和2之间吗?
- tab_3是否可以接收
storage
个与值存储顺序不同的事件?(例如,如果我们存储3,4,1,2,我们可以接收1,2,3,4,甚至以随机顺序如4,1,2,3)
找到答案:
- 目前,所有的问题都是否定的,但标准并不明确这种行为,所以这种行为可能会改变.你可以通过在控制台中的一些页面上运行这个代码来判断这个行为是否仍然相同,比如https://www.google.com(浏览器一开始可能会拒绝打开太多的标签,并可能会询问它是否可以,所以你可能需要允许打开测试页面的许多标签,比如第https://www.google.com页)
// We want to catch possible races, hence
// `testKey` is as short as possible to make it faster to process
// and increase operations per second and chance of race
const testKey = "0";
// Removing `testKey`, so that previous tests didn't interfere with this one,
// even if we didn't finish running them
localStorage.removeItem(testKey);
const runCount = 10_000;
// We want to catch possible races.
// To increase chance of race, we want as many parallel threads as there are CPU-s,
// because when there are more than 1 thread per CPU core,
// CPU will spend time not on racing code logic, but CPU context switch
const cpuCount = navigator.hardwareConcurrency;
const expectedTotalRunCount = cpuCount * runCount;
let totalRunCount = 0;
const storageEventListInOrderOfReceive = [];
window.addEventListener("storage", (storageEvent) => {
storageEventListInOrderOfReceive.push(storageEvent);
// Checking if we processed all events, caused by our test,
// and if we did, running assertions
if (
storageEvent.key === testKey &&
++totalRunCount === expectedTotalRunCount
) {
const changeRecordList = storageEventListInOrderOfReceive.map(
(storageEvent) => {
let oldTabNumber;
let oldRunNumber;
if (storageEvent.oldValue !== null) {
const oldTabNumberAndRunNumber = storageEvent.oldValue.split(",");
oldTabNumber = Number.parseInt(oldTabNumberAndRunNumber[0]);
oldRunNumber = Number.parseInt(oldTabNumberAndRunNumber[1]);
} else {
oldTabNumber = null;
oldRunNumber = null;
}
const newTabNumberAndRunNumber = storageEvent.newValue.split(",");
const newTabNumber = Number.parseInt(newTabNumberAndRunNumber[0]);
const newRunNumber = Number.parseInt(newTabNumberAndRunNumber[1]);
return {
oldTabNumber,
newTabNumber,
oldRunNumber,
newRunNumber,
};
},
);
console.log(changeRecordList);
let areEventsSequential = true;
for (let i = 0; i < changeRecordList.length - 1; i++) {
const currentChangeRecord = changeRecordList[i];
const nextChangeRecord = changeRecordList[i + 1];
let shouldLog = false;
if (currentChangeRecord.newTabNumber !== nextChangeRecord.oldTabNumber) {
areEventsSequential = false;
shouldLog = true;
console.error(
`currentChangeRecord.newTabNumber !== nextChangeRecord.oldTabNumber`,
);
}
if (currentChangeRecord.newRunNumber !== nextChangeRecord.oldRunNumber) {
areEventsSequential = false;
shouldLog = true;
console.error(
`currentChangeRecord.newRunNumber !== nextChangeRecord.oldRunNumber`,
);
}
if (currentChangeRecord.newRunNumber !== runCount) {
if (
currentChangeRecord.newTabNumber !== nextChangeRecord.newTabNumber
) {
areEventsSequential = false;
shouldLog = true;
console.error(
`currentChangeRecord.newTabNumber !== nextChangeRecord.newTabNumber`,
);
}
if (
currentChangeRecord.newRunNumber !==
nextChangeRecord.newRunNumber - 1
) {
areEventsSequential = false;
shouldLog = true;
console.error(
`currentChangeRecord.newRunNumber !== nextChangeRecord.newRunNumber - 1`,
);
}
} else {
if (
currentChangeRecord.newTabNumber === nextChangeRecord.newTabNumber
) {
areEventsSequential = false;
shouldLog = true;
console.error(
`currentChangeRecord.newTabNumber === nextChangeRecord.newTabNumber`,
);
}
if (nextChangeRecord.newRunNumber !== 1) {
areEventsSequential = false;
shouldLog = true;
console.error(`nextChangeRecord.newRunNumber !== 1`);
}
}
if (shouldLog) {
console.error(
"currentIndex:",
i,
"currentChangeRecord:",
currentChangeRecord,
"nextChangeRecord:",
nextChangeRecord,
);
}
}
if (areEventsSequential) {
console.log("Are events sequential:", areEventsSequential);
} else {
console.error("Are events sequential:", areEventsSequential);
}
// Cleaning up `testKey`
localStorage.removeItem(testKey);
}
});
for (let cpuIndex = 0; cpuIndex < cpuCount; cpuIndex++) {
const tab = window.open(location.href);
const scriptElement = document.createElement("script");
scriptElement.textContent = `
const startParallelExecutionBroadcastChannel = new BroadcastChannel('${testKey}');
startParallelExecutionBroadcastChannel.onmessage = () => {
// To be sure that nothing is synchronized by some properties of \`BroadcastChannel\`,
// we run this logic as last item in event loop
setTimeout(() => {
for(let runNumber = 1; runNumber < ${runCount + 1}; runNumber++) {
localStorage.setItem('${testKey}', \`${cpuIndex + 1},$\{runNumber}\`)
}
// Closing current tab to not do it manually
close();
}, 0);
};
`;
tab.document.head.appendChild(scriptElement);
}
const startParallelExecutionBroadcastChannel = new BroadcastChannel(testKey);
startParallelExecutionBroadcastChannel.postMessage(undefined);
console.log("Broadcasted `startParallelExecutionBroadcastChannel`");