15

What I want to accomplish:

  • gather artist id`s
    • either finding them in the db
    • or creating them
  • creating an event in the db, getting the event_id
  • waiting till both are done, artists and event id`s gathered
  • now looping over the artist, event combinations

What I got:

I`m working with Node and mysql. To insert the relations I have to wait for the artists to insert or create. I try to accomplish with the following code:

let promises = [];

if (artists.length != 0) {
    for (key in artists) {
        promises.push( find_artist_id_or_create_new_artist(artists[key]) )
    }
}

await Promise.all(promises);

Returning an id:

async function find_artist_id_or_create_new_artist(artist_name) {
    return await find_artist_return_id(artist_name, create_artist_return_id)
} 

Finding an artist:

async function find_artist_return_id(artist_name, callback) {
    var sql = "SELECT * FROM `artists` WHERE `name` LIKE "+con.escape(artist_name)+" LIMIT 1;"

    con.query(sql, (err,row) => {
      if(err) throw err;

      if (row.length == 0) {
        return callback(artist_name)
      } else {
        return row[0].id
      }
    });
}

Creating an artist

async function create_artist_return_id(artist_name) {
    var sql = "INSERT INTO `artists` (`id`, `name`, `meta_1`, `meta_2`) VALUES (NULL, "+con.escape(artist_name)+", NULL, NULL)";

    con.query(sql, (err, result) => {
      if(err) throw err;

      return result.insertId
    });
}

I understand that I cant return in a con.query function, but I dont how to properly setup the code to get this done. A link to, or help how to search for an answer is appreciated.

bart great
  • 153
  • 1
  • 1
  • 5
  • 1
    Mixing mySql callbacks and promises won't work. An easy solution is to use a wrapper for mySql that's already got the callback converted into Promises.. eg.. https://www.npmjs.com/package/promise-mysql – Keith Apr 03 '18 at 09:52
  • Thanks, can you recommend a source when async can and cant work? – bart great Apr 03 '18 at 09:56
  • Your fundamental `SQL functions` need to be converted to `promises` in order to be `awaited`. – Arman Charan Apr 03 '18 at 10:00
  • 1
    `await` can wait on any Promise, `async` is just a means of making a function return a `Promise`, but if inside this function you use callbacks you have broken the Promise. So in above if you just using the Promise version of mySQL you would do -> `var result = await con.query(sql)` instead, basically the callbacks are not used. – Keith Apr 03 '18 at 10:00

3 Answers3

11

Your fundamental SQL functions need to be converted to promises in order to be awaited.

See Async Function, Promise and Array.prototype.map() for more info.

// Artist Ids.
const artistIds = await Promise.all(artists.map(async (artist) => await findArtist(artist) || await createArtist(artist)))

// Find Artist.
const findArtist = artist => new Promise((resolve, reject) => con.query(`SELECT * FROM \`artists\` WHERE \`name\` LIKE ${con.escape(artist)} LIMIT 1;`, async (error, row) => {
  if(error) return reject(error)
  if (!row.length) return resolve(await createArtist(artist)) 
  return resolve(row[0].id)
}))

// Create Artist.
const createArtist = artist => new Promise((resolve, reject) => con.query(`INSERT INTO \`artists\` (\`id\`, \`name\`, \`meta_1\`, \`meta_2\`) VALUES (NULL, ${con.escape(artist)}, NULL, NULL)`, (error, result) => {
  if (error) return reject(error)
  return resolve(result.insertId)
}))
Arman Charan
  • 5,669
  • 2
  • 22
  • 32
  • 1
    Nice one :) just expand the explanation, then its the perfect answer ... – Jonas Wilms Apr 03 '18 at 10:28
  • 1
    Worked, to get it working these are the things I had to edit: add await: const artistIds = await Promise.all(artists.map(async (artist) => await findArtist(artist) || await createArtist(artist))) add argument: const createArtist = (artist_name) => new Promise((resolve, reject) => con.query(`INSERT INTO \`artists\` (\`id\`, \`name\`, \`meta_1\`, \`meta_2\`) VALUES (NULL, ${con.escape(artist_name)}, NULL, NULL)`, (error, result) => { if (error) return reject(error) return resolve(result.insertId) })) can you edit this and that i then accept this as the answer? – bart great Apr 03 '18 at 13:09
  • My bad ahah. Thanks for pointing those out! Glad it worked @bartgreat – Arman Charan Apr 03 '18 at 13:20
2

You just need to wrap the mysql callbacks into promises:

 function find_artist_return_id(artist_name) {
  return new Promise((resolve, reject) => {
     var sql = "SELECT * FROM `artists` WHERE `name` LIKE "+con.escape(artist_name)+" LIMIT 1;"

      con.query(sql, (err,row) => {
         if(err) return reject(err);

         if (row.length == 0) {
           return resolve(artist_name);

           return resolve(row[0].id);      
      });
   });
}

And by the way, this is very ugly:

 if (artists.length != 0) {
   for (key in artists) {

Just do:

  for(const artist of artists)
    promises.push(someApiCall(artist));

or:

  const promises = artists.map(someApiCall);
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
0

The Easy way is to use already existing ORM like sequalizejs or etc they return promise for you and instead of running find and create in separate Raw query in native MySQL driver. You can simply use API'S like find or create something.

i explain you how async works in your example , i took a piece of code from your example.

    async function createArtist(artist_name) {
    var sql = "INSERT INTO `artists` (`id`, `name`, `meta_1`, `meta_2`) VALUES (NULL, "+con.escape(artist_name)+", NULL, NULL)";

    con.query(sql, (err, result) => {
        if(err) throw err;

        return result.insertId
    });
}

const artistId = (async () => {
    await createArtist('maxi');
})();

look into createArtist function this is exactly what you have . Three things you have to note here,

  1. you declare it a async function so it return promise in default.
  2. it can simply return anything like normal function ,if it is a synchronous function avoid using async.
  3. As you said you cannot return anything from callback . since it is asynchronous you have to return promise.

so the code can be changed to

    async function createArtist(artist_name) {
    return new Promise((resolve,reject)=>{
        var sql = "INSERT INTO `artists` (`id`, `name`, `meta_1`, `meta_2`) VALUES (NULL, "+con.escape(artist_name)+", NULL, NULL)";

        con.query(sql, (err, result) => {
            if(err) reject(err);
            resolve(result.insertId)
        });
    });
}

const artistId = (async () => {
    await createArtist('maxi');
})();

Things changed here , added native promise wrapper and return it, before it goes asynchronous. called resolve to make success . And reject to make it fail.

Don't forget to add try ...catch blog for await's to handle error.

Vignesh
  • 496
  • 1
  • 4
  • 13