JavaScript,如何等待多个 promises?

16

我想要实现的:

  • 收集艺术家ID
    • 在数据库中查找它们
    • 或者创建它们
  • 在数据库中创建一个事件,获取事件ID
  • 等待直到收集完艺术家和事件ID
  • 现在循环处理每个艺术家和事件组合

我得到了以下代码:

我正在使用Node和MySQL。为了插入关系,我必须等待艺术家的插入或创建。我尝试使用以下代码完成:

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);

返回id:

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

寻找一位艺术家:

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
      }
    });
}

创造一个艺术家

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
    });
}

我明白在con.query函数中无法返回,但我不知道如何正确设置代码以完成此操作。感激提供回答的链接或搜索方法。


1
混合使用mySql回调和Promises是行不通的。一个简单的解决方案是使用一个已经将回调转换为Promises的mySql包装器,例如 https://www.npmjs.com/package/promise-mysql。 - Keith
谢谢,你能推荐一个关于异步操作何时适用和不适用的资源吗? - bart great
你的基本 SQL函数 需要被转换为 promises 以便能够被 awaited - Arman Charan
1
await 可以等待任何 Promise, async 只是使函数返回一个 Promise 的一种方式,但如果在此函数内部使用回调函数,则会破坏 Promise。因此,在上述情况下,如果您只使用 mySQL 的 Promise 版本,则应该这样做 -> var result = await con.query(sql),基本上不使用回调函数。 - Keith
3个回答

12

为了能够await,您的基本SQL函数需要转换为promises

有关更多信息,请参见Async FunctionPromiseArray.prototype.map()

// 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)
}))

1
不错 :) 只需扩展一下解释,就是完美的答案了... - Jonas Wilms
1
工作了,为了让它正常工作,我必须编辑以下内容:添加await:const artistIds = await Promise.all(artists.map(async (artist) => await findArtist(artist) || await createArtist(artist)))添加参数: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) }))您能否编辑一下,然后我接受这个答案? - bart great
我的不好啊哈哈。感谢你指出那些问题!很高兴它可以工作 @bartgreat - Arman Charan

2
只需将MySQL的回调函数包装成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);      
      });
   });
}

顺便说一下,这非常丑陋:

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

只需执行:

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

或者:

  const promises = artists.map(someApiCall);

0

简单的方法是使用已经存在的ORM,如sequalizejs等,它们会为您返回承诺,而不是在本机MySQL驱动程序中分别运行find和create的原始查询。您可以简单地使用API'S,例如查找或创建某些内容。

我向您解释了异步在您的示例中如何工作,我从您的示例中取了一段代码。

    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');
})();

查看 createArtist 函数,这就是你所需要的。这里有三件事情需要注意:

  1. 你声明了它是一个异步函数,因此默认返回 promise。
  2. 如果它是同步函数,则可以像普通函数一样简单地返回任何东西,避免使用 async。
  3. 由于它是异步的,你不能从回调函数中返回任何内容。因此必须返回 promise。

所以代码可以改为:

    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');
})();

这里有些变化,添加了本地的 Promise 包装器并返回它,在异步之前调用 resolve 来表示成功,调用 reject 来表示失败。

别忘了为 await 添加 try...catch 块以处理错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接