3

I'm trying to return a custom object from a async function that works as wrapper for a put using indexdb.

Using Promises this is easy. However, using async/await became more challenging...

const set = async (storeName, key, value) => {


    if (!db)
        throw new Error("no db!");

    try {

        const result = {};

        let tx = db.transaction(storeName, "readwrite");
        let store = tx.objectStore(storeName);
        let r = store.put({ data: key, value: value });

        console.log(r);

        r.onsuccess = async () => {
            console.log('onsuccess');
            result.something = true;
        }
        r.onerror = async () => {
            console.log('onerror');
            result.something = false;
        }

        await r.transaction.complete;  // ok... this don't work

        // how can I await until onsuccess or onerror runs?

        return result;

    } catch (error) {
        console.log(error);
    }
}

The ideia is to return a composed object... however all my attemps fails as onsuccess runs after returning the result.

I googled a lot and could't find a way to proper await for onsuccess/onerror events.

I know that returning a Promise is more easy as resolve(result) would end returning what I want... but i'm trying to learn to make same code using async/await.

Thank you so much,

3 Answers 3

5

Try this:

function set(db, storeName, key, value) {
  return new Promise((resolve, reject) => {
    let result;
    const tx = db.transaction(storeName, 'readwrite');
    tx.oncomplete = _ => resolve(result);
    tx.onerror = event => reject(event.target.error);
    const store = tx.objectStore(storeName);
    const request = store.put({data: key, value: value});
    request.onsuccess = _ => result = request.result;
  });
}

async function callIt() {
  const db = ...;

  const result = await set(db, storeName, key, value);
  console.log(result);
}

Edit, since you insist on using the async qualifier for the set function, you can do this instead. Please note I find this pretty silly:

async function set(db, storeName, key, value) {
  // Wrap the code that uses indexedDB in a promise because that is 
  // the only way to use indexedDB together with promises and 
  // async/await syntax. Note this syntax is much less preferred than 
  // using the promise-returning function pattern I used in the previous 
  // section of this answer.
  const promise = new Promise((resolve, reject) => {
    let result;
    const tx = db.transaction(storeName, 'readwrite');
    tx.oncomplete = _ => resolve(result);
    tx.onerror = event => reject(event.target.error);
    const store = tx.objectStore(storeName);
    const request = store.put({data: key, value: value});
    request.onsuccess = _ => result = request.result;
  });

  // We have executed the promise, but have not awaited it yet. So now we 
  // await it. We can use try/catch here too, if we want, because the 
  // await will translate the promise rejection into an exception. Of course, 
  // this is also rather silly because we are doing the same thing as just 
  // allowing an uncaught exception to exit the function early.
  let result;
  try {
    result = await promise;
  } catch(error) {
    console.log(error);
    return;
  }

  // Now do something with the result
  console.debug('The result is', result);
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you Josh. Lots of documentation refer to async/await as a new es6 way to implement promise based solutions. I agree that using async qualifier to wrap a promise looks a little silly. However, the goal was not returning the indexedDB result as is, but a new object based on the result and adding some extra logic triggered by oncomplete and onerror events.
@dfelix async and await are how you use promises, but not how you create them. An async function return value is a promise. Even when you return a primitive number, it is wrapped in a promise. This is is why all async functions must be awaited if you want the settled value of the promise. You are making a distinction where one does not exist.
2

Ultimately you'll end up wrapping IDB in a promise-friend library, but for your specific need, you could use something like this:

function promiseForTransaction(tx) {
  return new Promise((resolve, reject) => {
    tx.oncomplete = e => resolve();
    tx.onabort = e => reject(tx.error);
  });
}

And then in your code you can write things such as:

await promiseForTransaction(r.tx);

... which will wait until the transaction completes, and throw an exception if it aborts. (Note that this requires calling the helper before the transaction could possibly have completed/aborted, since it won't ever resolve if the events have already fired)

Comments

1

I can't confirm it right now but I think it should be await tx.complete instead of await r.transaction.complete;.

But a general solution that would work even if the API would not support Promises directly would be to wrap a new Promise around the onsuccess and onerror and use await to wait for that Promise to resolve, and in your onsuccess and onerror you then call the resolve function:

const set = async (storeName, key, value) => {


  if (!db)
    throw new Error("no db!");

  try {

    const result = {};

    let tx = db.transaction(storeName, "readwrite");
    let store = tx.objectStore(storeName);
    let r = store.put({
      data: key,
      value: value
    });

    console.log(r);

    await new Promise((resolve, reject) => {
      r.onsuccess = () => {
        console.log('onsuccess');
        result.something = true;
        resolve()
      }

      r.onerror = () => {
        console.log('onerror');
        result.something = false;
        // I assume you want to resolve the promise even if you get an error
        resolve()
      }
    })

    return result;
  } catch (error) {
    console.log(error);
  }
}

I would furhter change it to:

try {
  await new Promise((resolve, reject) => {
    r.onsuccess = resolve
    r.onerror = reject
  })

  console.log('success');
  result.something = true;
} catch(err) {
  console.log('error');
  result.something = false;
}

1 Comment

Resolving the promise on request success is premature. In the case of a write transaction, the promise should settle only when the transaction completes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.