The indexedDB has a spec saying that you can access an indexed database synchronously, but it hasn't been implemented yet.
I was just wondering if there is a way to make it synchronous manually,
My JavaScript looks like this,
var trans = databaseAsync.transaction(["mapTile"], IDBTransaction.READ_WRITE);
var store = trans.objectStore("mapTile");
var keyRange = IDBKeyRange.bound(evt.data[0], evt.data[0]);
var cursorRequest = store.openCursor(keyRange);
// can you put some kind of wait in here?
cursorRequest.onsuccess = function(e)
{
var result = e.target.result;
if(!!result == false)
{
}
}
So can you put something in there to make it wait until the onsuccess method has been called before continuing on?
The reason why I want to do this is the code above is wrapped inside this method,
dojo.extend(esri.layers.ArcGISTiledMapServiceLayer, {
getTileUrl : function(level, row, col)
{
// blah
return url;
}
So it is an ESRI tile layer (which will load tiles onto a map on my web page), and that method needs to return the url straight away for a particular tile. It will either be a URL to load the image from if it isn't cached in the database already, or this,
data:image;base64,*BASE64DATA*
Where BASE64DATA is the data from the database if previously cached.
I was previously using the localStorage for this, which works synchronously, but that has a 5MB limit so I thought I would experiment with indexedDB.
The indexedDB has a spec saying that you can access an indexed database synchronously, but it hasn't been implemented yet.
I was just wondering if there is a way to make it synchronous manually,
My JavaScript looks like this,
var trans = databaseAsync.transaction(["mapTile"], IDBTransaction.READ_WRITE);
var store = trans.objectStore("mapTile");
var keyRange = IDBKeyRange.bound(evt.data[0], evt.data[0]);
var cursorRequest = store.openCursor(keyRange);
// can you put some kind of wait in here?
cursorRequest.onsuccess = function(e)
{
var result = e.target.result;
if(!!result == false)
{
}
}
So can you put something in there to make it wait until the onsuccess method has been called before continuing on?
The reason why I want to do this is the code above is wrapped inside this method,
dojo.extend(esri.layers.ArcGISTiledMapServiceLayer, {
getTileUrl : function(level, row, col)
{
// blah
return url;
}
So it is an ESRI tile layer (which will load tiles onto a map on my web page), and that method needs to return the url straight away for a particular tile. It will either be a URL to load the image from if it isn't cached in the database already, or this,
data:image;base64,*BASE64DATA*
Where BASE64DATA is the data from the database if previously cached.
I was previously using the localStorage for this, which works synchronously, but that has a 5MB limit so I thought I would experiment with indexedDB.
Share Improve this question edited May 25, 2014 at 17:10 Josh 18.7k7 gold badges54 silver badges72 bronze badges asked Feb 9, 2012 at 22:39 peterpeter 13.5k24 gold badges85 silver badges143 bronze badges 1- 6 year 4 months later...this question still holds value and looks for a decent updated answer!! – NoobEditor Commented Jun 26, 2018 at 4:39
5 Answers
Reset to default 3IndexedDB Sync API is marked as a risky part of the IndexedDB specification and they might be removed due to potential lack of implementations.
I've implemented 'sync' solution using the 'oncomplete' transaction event which guaranties that the current action is finished before starting the next one, and I also use custom semaphore and queue logic which handles the async calls from GUI and ensures that 2 open connections towards IndexedDB database won't happen at the same time.
There is a more general question how to wait for asynchronous functions where you will find an answer: How to block on asynchronous functions in JavaScript
I also found a nice waitFor routine inside Trial Tool.
IE10 supports the sync api, but the indexeddb sync api can only be used inside a web worker. If you are working in the UI thread you need to use the async API.
Easiest way to work with the async API is through promises.
2024 Answer:
Now that we have SharedArrayBuffer
, and Atomics.wait()
, this should be possible even on the main thread, but I haven't tested it.
It's naturally going to be quite hacky, since browsers try to get you to avoid doing synchronous storage access, since it can be slow (i.e. up to tens of milliseconds - vs memory/RAM access which is several orders of magnitude faster) and will cause the main thread to lag while the data is being fetched.
Here's what I think you'd need to do:
- Ensure you're serving your HTML page with COOP/COEP headers which will cross-origin isolate it, which is needed to allow use of
SharedArrayBuffer
- Create a worker and send it your
SharedArrayBuffer
, which must be large enough space for "communication" bytes (to signal to the worker that you're making a request for data), and for the largest reasonable number of bytes returned from IndexedDB - e.g. 100MB (since data transfer will happen via thisSharedArrayBuffer
, and not viapostMessage
). Instead of pre-allocating 100MB, you could probably dynamically grow it as needed withSharedArrayBuffer.grow()
. - From the main thread, send a request to the worker via your communication bytes and immediately enter a while loop which checks the communication bytes at each iteration of the loop to see if the worker has finished. The worker notices the communication bytes change via
Atomics.wait()
and starts getting the requested data asynchronously fromIndexedDB
. - Once the worker is finished getting the data, it puts the data in the
SharedArrayBuffer
and appropriately updates the communication bytes to notify the main thread that it's finished. - The main thread's while loop stops since it notices the communication bytes change, and it grabs the data that the worker put in the
SharedArrayBuffer
, and that's it. You've accessed IndexedDB data from the main thread withoutawait
/async
/Promise
.
Notes:
- You'd want to make sure your error checking is good/reliable on the worker side, since if it breaks somehow, then your main thread will be permanently stuck in the while loop waiting for the data that will never come. I suppose you could also add a check against
Date.now()
in the main thread's while loop to ensure that if it takes more than e.g. 5 seconds, it's break out of the loop and throw an error.
IndexedDB is still around, and it's likely one of the best ways to store data for apps that run in users' browsers. One good thing about IndexedDB is that it lets you keep data as native JavaScript objects.
IndexedDB is made on the way that tasks can be done without blocking the main thread. A responsive UI needs asynchronous operations to keep it running, especially for tasks that might take a while to do because they interact with storage.
async function readValue(dbName, storeName, key) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName);
request.onerror = (event) => {
reject(event.target.error);
};
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const getRequest = store.get(key);
getRequest.onerror = (event) => {
reject(event.target.error);
};
getRequest.onsuccess = (event) => {
resolve(getRequest.result);
};
};
});
}
// Usage
(async () => {
try {
const value = await readValue('db', 'main', 'bla');
console.log(value);
} catch (error) {
console.error(error);
}
})();