skip to Main Content

I am writing a lyrics app, that saves a song book and the songs in that book to an SQL lite database. I am mapping through an array of books and calling an API to save the book’s songs.
I want the saveBook() function to fully complete before moving on to the saveBookSong() function. I have tried async await but it is not working. Any help would be greatly appreciated,
Thanks, Sam

useEffect( () => {
   Books.map ((book) =>{
    saveBook(book)
    saveBookSong(book.songId.toString())
  })
 function saveBook(book) {
   db.transaction( (tx) => {
    tx.executeSql(
        `INSERT INTO books (id, description, img, name, song_id)
         VALUES (?, ?, ?, ?, ?)`,
        [book.id, book.description, book.img, book.name, book.songId],
        () => {
          console.log("Saved!!", book.id)
        }
    );
  });
}
 function saveBookSong(id) {
   axios
    .get(`songs/${id}`)
    .then((bookSong) => {
      bookSong.data.forEach((song) => {
        db.transaction((tx) => {
          tx.executeSql(
            `INSERT INTO songs (id, book, book_id, data, int_song_number, lang, search_title, song_number, title, url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
            [
              song.id,
              song.book,
              song.bookId,
              song.data,
              song.intSongNumber,
              song.lang,
              song.searchTitle,
              song.songNumber,
              song.title,
              song.url,
            ],
            () => {
            }
          );
        });

      });
    })
    .catch((err) => {
      console.log(err);
    });
}

2

Answers


  1. If I’m not mistaken .map() is not async aware. So I would suggest two solutions to your problem:

    1)

    useEffect( () => {
       Books.map ((book) =>{
        saveBook(book).then(() => saveBookSong(book.songId.toString()))
      }, [Books])
    

    In this case you go with then after first function is executed.

    2)

    useEffect( () => {
    const manageBooks = async() => {
     for (book of Books) {
           await saveBook(book);
           await saveBookSong(book.songId.toString());
     }
    }
          }, [Books])
    

    You can go with async for loop which will wait for executing first task before moving to another one

    Login or Signup to reply.
  2. I’m assuming you want all the books to be saved in parallel. I would do this:

    useEffect( () => {
        const bookSavings = Books.map(async (book) =>{
            await saveBook(book)
            await saveBookSong(book.songId.toString())
        });
        // If you want to wait for all books to be saved
        await Promise.all(bookSavings);
        // ... Whatever else you want to do in your useEffect
    }, [Books])
    

    Alright, but you can’t simply await saveBook and saveBookSong because they are not async functions. Let’s move forward.

    Under the hood, async functions are just functions that return promises. We can actually await any promise. Therefore, you can make saveBook and saveBookSong awaitable by wrapping them in promises, like this:

    function saveBook(book) {
        return new Promise((resolve) => {
            db.transaction( (tx) => {
                tx.executeSql(
                    `INSERT INTO books (id, description, img, name, song_id)
                     VALUES (?, ?, ?, ?, ?)`,
                    [book.id, book.description, book.img, book.name, book.songId],
                    () => {
                        console.log("Saved!!", book.id)
                        resolve(); // <- THIS IS IMPORTANT
                    }
                );
            });
        });
    }
    

    EDIT:
    The second function requires a bit extra caring because, like you pointed out, it contains a loop. However, that’s not really different from the top-level .map function that you had. Here’s my (untested) suggestion, hopefully you can work from here. I splitted the function in two, to make it more understandable:

    async function saveBookSong(id) {
        const bookSong = await axios.get(`songs/${id}`);
        const savePromises = bookSong.data.map(song => {
            return saveSong(song);
        });
        await Promise.all(savePromises);
    }
    
    function saveSong(song) {
        return new Promise((resolve) => {
            db.transaction((tx) => {
                tx.executeSql(
                    `INSERT INTO songs (id, book, book_id, data, int_song_number, lang, search_title, song_number, title, url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
                    [
                        song.id,
                        song.book,
                        song.bookId,
                        song.data,
                        song.intSongNumber,
                        song.lang,
                        song.searchTitle,
                        song.songNumber,
                        song.title,
                        song.url,
                    ],
                    resolve
                );
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search