通过使用`co`包装它们来使回调异步的正确方法?

时间:2016-02-25 20:53:00

标签: node.js asynchronous generator co node-commander

到2016年,Node自v4以来几乎已经完全支持ES6,而Promise自0.12以来一直存在。现在是时候把回调留在IMO尘埃中了。

我正在使用基于commander.js的CLI工具,它利用了很多异步操作 - http请求和用户输入。我希望将Commander action包装在异步函数中,以便它们可以被视为promises,并且还支持生成器(对于我用于用户输入的co-prompt库非常有用)。

我尝试用co以两种方式包装CB:

1) program.command('myCmd') .action(program => co(function* (program) {...}) .catch(err => console.log(err.stack)) );

2)program.command('myCmd').action(co.wrap(function* (program) { .. }));

1)的问题是program参数未通过

2)的问题是误吞了......

我真的很想让它工作,因为它在我的用例中产生了更好的代码 - 涉及大量的http请求,还等待使用co-prompt库的用户输入..

或许以某种方式包裹program.Command.prototype.action是一个更好的选择吗?

谢谢!

2 个答案:

答案 0 :(得分:0)

我使用了co之类的定制版本来获取db.exec函数,该函数使用yield来执行数据库请求。您可以将参数传递给生成器函数(我传入连接对象 - 请参阅我所做的注释)。

这是db.exec函数,它与co非常相似

exec(generator) {
  var self = this;
  var it;
  debug('In db.exec iterator');
  return new Promise((accept,reject) => {
    debug('In db.exec Promise');
    var myConnection;
    var onResult = lastPromiseResult => {
      debug('In db.exec onResult');
      var obj = it.next(lastPromiseResult);
      if (!obj.done) {
        debug('db.exec Iterator NOT done yet');
        obj.value.then(onResult,reject);
      } else {
        if (myConnection) {
          myConnection.release();
          debug('db.exec released connection');
        }
        accept(obj.value);
        debug('db.exec Promise Resolved with value %d',obj.value);
      }
    };
    self._connection().then(connection => {
      debug('db.exec got a connection');
      myConnection = connection;
      it = generator(connection); //This passes it into the generator
      onResult();  //starts the generator
    }).catch(error => {
      logger('database', 'Exec Function Error: ' + error.message);
      reject(error);
    });
  });
}

连接对象也由数据库连接对象包装,并提供生成器函数处理数据库结果行的能力,但我不会在这里发布(尽管下面的示例使用它来处理行)。

以下是使用exec函数运行sql序列

的示例
    db.exec(function*(connection) {
      if (params.name === ADMIN_USER) {
        debug('Admin Logon');
        user.name = ADMIN_DISPLAY;
        user.keys = 'A';
        user.uid = 0;
        let sql = 'SELECT passwordsalt FROM Admin WHERE AdminID = 0';
        connection.request(sql);
        yield connection.execSql(function*() {
          let row = yield;
          if (row) {
            user.nopass = (row[0].value === null);
          } else {
            user.nopass = false;
          }
          debug('Admin Password bypass ' + user.nopass.toString());
        });
      } else {
        debug('Normal User Logon');
        let sql = `SELECT u.UserID,PasswordSalt,DisplayName,AccessKey,l.LogID FROM Users u
          LEFT JOIN UserLog l ON u.userID = l.userID AND DATEDIFF(D,l.LogDate,GETDATE()) = 0
          WHERE u.UserName = @username`;
        let request = connection.request(sql);
        request.addParameter('username',db.TYPES.NVarChar,params.name);
        let count = yield connection.execSql(function*() {
          let row = yield;
          if (row) {
            user.uid = row[0].value;
            user.name = row[2].value;
            user.keys = (row[3].value  === null) ? '' : row[3].value;
            user.nopass = (row[1].value === null) ;
            user.lid = (row[4].value === null) ? 0 : row[4].value;
            debug('Found User with uid = %d and lid = %d, keys = %s',
              user.uid, user.lid, user.keys);
          }
        });
        if (count === 0) {
          debug('Not Found User');
          // couldn't find name in database
          reply(false,false);
          return;
        }
      }
      if (!user.nopass) {
        debug('Need a Password');
        //user has a password so we must check it
        passGood = false; //assume false as we go into this
        let request = connection.request('CheckPassword');
        request.addParameter('UserID',db.TYPES.Int,user.uid);
        request.addParameter('password',db.TYPES.VarChar,params.password);
        yield connection.callProcedure(function*() {
          let row = yield;
          if (row) {
            //got a valid row means we have a valid password
            passGood = true;

          }
        });
      } else {
        passGood = true;
      }
      if (!passGood) {
        debug('Not a Good Pasword');
        reply(false,true);
      } else {
        if (user.uid !== 0 && user.lid === 0) {
          let sql = `INSERT INTO UserLog(UserID,LogDate,TimeOn,UserName) OUTPUT INSERTED.logID
           VALUES(@uid,GETDATE(),GETDATE(),@username)`;
          let request = connection.request(sql);
          request.addParameter('uid',db.TYPES.Int,user.uid);
          request.addParameter('username',db.TYPES.NVarChar,user.name);
          yield connection.execSql(function*() {
            let row = yield;
            if (row) {
              user.lid = row[0].value;
              debug('Users Log Entry = %d',user.lid);
            }
          });
        }
        reply(true,user);
      }
    })
    .catch((err) => {
      logger('database','Error on logon: ' + err.message);
      reply(false,false);
    });
  });

答案 1 :(得分:0)

在 Commander.js 中有一个非常简单的方法来执行 async 函数

async function run() { 
  /* code goes here */  
}

program
  .command('gettime')
  .action(run);
  
program.parse(process.argv);