在Node.js中,如何从一个函数返回一个Promise?

5

我正在探索在node.js中使用promises和callbacks的可能性。我尝试找到一种方法使得这段代码能够工作。目前我面临的问题是,当我调用一个函数并想要使用返回值时,它还没有准备好。我知道我该怎么做,但不知道如何做。基本上,我必须让insertAddress()返回一个promise(这样我就可以在其上使用.then()),或者将回调作为参数传递给它。为了实现这一点,我认为databaseWork()也应该返回一个promise。但我不知道在哪里添加它。问题出现在'console.log(out)'中,因为insertAddress仍在运行,out变量还没有被设置。下面是我的代码:

app.js
-----

const databaseWork = require('./db/mysql.js').databaseWork;

app.use('/test',  (req, resp) => {
  var address = {
     country : "Country",
     city : "Randomcity",
     street : "Random",
     number : 6,
     postalcode : "A789",
     province : "a province"
   }
  var out = insertAddress(address);  //<== takes time to finish, is not ready when the next console.log finishes
  console.log(out);
});

function insertAddress(address){
    var rows
    databaseWork(
      //Following anonymous function contains the actual workload. That has to be done inside a transaction
       async (connection) => {
       rows = await insertAddressQuery(address,connection);
       console.log(rows); //this one waits for insertAddressQuery to be complete
    })
    return rows; //this will run before insertAddressQuery is complete
}


function insertAddressQuery(address,connection) {
    return new Promise( (resolve, reject) => {
    //async job
      connection.query('INSERT INTO address (country,city,Street,number,postalcode,province) VALUES(?,?,?,?,?,?)', [address.country,'4','5',6,'7','8'] , (err, rows) => {
            if (err) {reject(err);}
              resolve(rows);
        });
    });
};


/db/mysql.js
------------

var mysql = require('mysql');
var dbpool = mysql.createPool({
  host: process.env.HOST_DB,
  user: process.env.USER_DB,
  password: process.env.PWD_DB,
  database: process.env.DB
});

function databaseWork(workload){
  dbpool.getConnection( async (err, connection) => {
      await beginTransaction(connection);

      await workload(connection);

      await commitTransaction(connection)
      connection.release();
    });
}


function beginTransaction(connection){
  return new Promise( (resolve, reject) => {
    //async job
    connection.beginTransaction( (err) => {
      if (err) {reject(err);}
        resolve();
    });
  });
};



function commitTransaction(connection) {
    return new Promise( (resolve, reject) => {
    //async job
    connection.commit( (err) => {
        if (err) {reject(err);}
          resolve();
        });
    });
};

exports.databaseWork = databaseWork;
2个回答

6
您可以在您的数据库工作中完成此操作:
function databaseWork(workload) {
  return new Promise((resolve, reject) => {
    dbpool.getConnection(async (err, connection) => {
      try {
        await beginTransaction(connection);

        var result = await workload(connection);

        await commitTransaction(connection)

        resolve(result);
      } catch( err ) {
         reject(err)
      } finally {
        connection.release();
      }
    });
  })

}
< p > databaseWork 返回的 Promise 将会由 workload 的结果解决。现在你可以将 insertAddress 更改为以下内容:

async function insertAddress(address){
    return databaseWork(connection => {
       return insertAddressQuery(address,connection);
    })
}

您需要将路由更改为以下内容:
app.use('/test', async (req, resp) => {
  var address = {
    country: "Country",
    city: "Randomcity",
    street: "Random",
    number: 6,
    postalcode: "A789",
    province: "a province"
  }
  var out = await insertAddress(address); // use await here to wait for insertAddress to be finished

  console.log(out);
});

*更新代码,使用返回Promise的getConnection函数:

function getConnection() {
  return new Promise((resolve, reject) => {
    dbpool.getConnection((err, connection) => {
      if (err) {
        reject(err)
      } else {
        resolve(connection);
      }
    })
  });
}

async function databaseWork(workload) {
  var connection = await getConnection();
  var result;
  try {
    await beginTransaction(connection)

    result = await workload(connection)

    await commitTransaction(connection)
  } catch (err) {
    // a rollback might be neccesaary at that place
    throw err
  } finally {
    connection.release();
  }

  return result;
}

1
@Bosiwow 但要注意错误处理。这个解决方案仍需要一些工作,因为我没有修复我们代码中的其他缺陷。您没有检查getConnection是否有错误。而且,如果workload失败,您可能希望在catch块中添加一个rollback。这段代码只是为了给您一个大致的想法,如何解决它。您甚至可以考虑创建getConnection的异步版本,以使您的databaseWork更清晰。 - t.niese
我能不能把 'dbpool.getConnection(async (err, connection) => {' 放在 try 块的内部?我能在 catch 块中执行回滚操作吗?如果 catch 块中的回滚操作失败了怎么办?这不好,对吧?那 finally 块中的释放操作呢?如果那个操作失败了怎么办? - Bosiwow
1
@Bosiwow 不,你不能将它放在try catch块中。dbpool.getConnection不会抛出异常,而是将错误传递给回调函数。我更新了问题,并添加了一个返回promise的getConnection。如果getConnection被拒绝,databaseWork不会被拒绝。至于你其他的问题,那已经超出了你最初问题的范围。如果你在SO上找不到答案,那么你可以开一个新问题。 - t.niese
你好,非常感谢您的回复。您说:“如果 getConnection 被拒绝,databaseWork 不会被拒绝。” 如果我理解正确,当 getConnection() 被拒绝时,就不会有连接对象。因此,“await beginTransaction(connection)” 也将被拒绝。但这不会触发 catch,是吗?编辑:好的,当 promise 被拒绝时,catch 就会被调用。https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch - Bosiwow
好的,如果一个 Promise 被拒绝了,就会调用 catch。所以现在 getConnection() 返回一个在出错时被拒绝的 Promise。那么我还是不能把它放在 try 里面吗? - Bosiwow
1
@Bosiwow 抱歉,那是个打字错误。现在使用 await,如果 getConnection 被拒绝,databaseWork 将被拒绝,现在你可以将 await getConnection 放在一个 try 块中,或者让 databaseWork 失败并显示 getConnection 的错误信息。 - t.niese

0

你可以使用async await来实现这一点。

var example = async (req, res) => {
    var response = await myAsyncTask();

    // this will get logged once the async task finished running.
    console.log(response)
}

// Use async await to get response
var myAsyncTask = async () => {
    try {
        var response = await asyncTaskINeedDataFrom()
        return response;
    }
    catch(err) {
        return console.log(err);
    }
}

这是npm模块:https://www.npmjs.com/package/async

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