在 Promise 中封装 MongoDB 个调用
Wrapping MongoDB calls within a Promise
我通常使用 Meteor (1.0.3),但对于一个特殊情况,我使用原始服务器端路由来呈现文件——所以我在 Meteor 方法之外。
我也在使用 node fs.writeFile/fs.readFile 和 exec 命令调用 Linux 命令行实用程序。
我提出这个问题的唯一一点是节点调用当然是异步的。因此,我选择使用节点 Q 库来管理异步回调。
这一切都有效,直到我添加了一行来调用 MongoDB 数据库。
这样的调用:
var record_name = Mongo_Collection_Name.findOne({_personId: userId}, {fields: {'_id': 0}});
产生以下错误:
[Error: Can't wait without a fiber]
只有当我将函数包装在 Promise 中时才会出现此错误。
例如,像这样的东西会抛出:
getRecordExample = function () {
var deferred = Q.defer();
var record_name = Mongo_Collection_Name.findOne({_personId: userId}, {fields: {'_id': 0}});
// do something
// if no error
deferred.resolve(record_name);
return deferred.promise;
}
如果我使用 Meteor Fibers 库,我不会收到错误消息:
getRecordExample = function () {
var deferred = Q.defer();
Fiber = Npm.require('fibers');
var record_name
Fiber(function () {
record_name = Mongo_Collection_Name.findOne({_personId: userId});
}).run()
// do something
// if no error
deferred.resolve(record_name);
return deferred.promise;
}
但是,record_name 变量在 fiber 之外是未定义的,所以据我所知,我没有办法将变量传递到 Fiber 范围之外。
一个更精确的例子
这有点长,所以您必须向下滚动才能看到全部内容。我基本上是在这里构建一个工作流,所以有流程和子流程。
// both/routes.js
Router.route('/get-route', function(req, res) {
// get the userId then start the workflow below
// using Promises here because these were firing concurrently
Q(userId)
.then(process_1)
.then(process_2)
.done();
}, { name: 'server-side-ir-route', where: 'server' }
// server.js
process_1 = function (userId) {
sub_process_1(userId);
sub_process_2(userId);
return userId;
}
process_2 = function (userId) {
sub_process_3(userId);
sub_process_4(userId);
return userId;
}
sub_process_1 = function (userId) {
var result = get_record_1(userId);
// do stuff with result
// using Q library to call out to async fs.writeFile, return Promise
fs_writeFile_promise(result)
.catch(function (error) {
console.log('error in sub_process_1_write', error);
})
.done(function () {
console.log('done with sub_process_1');
}
return userId;
}.future() // <-- if no future() here, the exception is thrown.
sub_process_2 = function (userId) {
var result = get_record_2(userId);
// do stuff with result
// using Q library to call out to async fs.writeFile, return Promise
fs_writeFile_promise(result)
.catch(function (error) {
console.log('error in sub_process_1_write', error);
})
.done(function () {
console.log('done with sub_process_1');
}
return userId;
}.future()
// async because of I/O operation (I think)
get_record_1 = function (userId) {
var record_1 = Mongo_Collection_Name.findOne({'userId': userId});
// do stuff
return record_1;
}
get_record_2 = function (userId) {
var record_2 = Mongo_Collection_Name.findOne({'userId': userId});
// do stuff
return record_2;
}
// async operation using Q library to return a Promise
fs_writeFile_promise = function (obj) {
var deferred = Q.defer();
fs.writeFile(obj.file, obj.datas, function (err, result) {
if (err) deferred.reject(err);
else deferred.resolve('write data completed');
});
return deferred.promise;
}
现在,让我们假设 process_2 函数与 process_1
完全一样
此外,我们应该假设我在每个函数中都有 console.log('step_start') 和 console.log('step_end')。这是它在命令行中的样子:
- 启动进程
- 结束进程
- 启动进程 1
- 结束进程 1
- 启动进程 2
- 启动子进程 1
- 正在获取记录 1
- 启动子进程 2
- 正在获取记录 2
- 返回记录 1
- 结束子进程 1
- 在子进程 1 中调用了 writeData
- 返回记录 2
- 在子进程 2 中调用了 writeData
- 结束进程 2
- 结束子流程 1
我必须在 sub_process_1() 函数上放置一个 Fiber (future) 的原因是,当我将函数 process_1() 放置在顶部的 Q 链中时,我得到了错误:没有光纤就等不及了。
如果我删除顶部 Q 链中的 process_1() 并从 sub_process_1() 中删除 .future() 则不会引发异常。
问题
- 为什么调用 Promise 中的 Mongo 集合会导致
Meteor 应用程序中的光纤错误?
- 在同步函数中调用异步函数通常会导致同步函数变成异步函数吗?
- 如何解决这个问题?
解决此问题的最常见方法是将使用 Meteor 函数的异步回调包装在 Meteor.bindEnvironment()
.
中
如果您使用的是 Meteor 内核 WebApp package to handle your server side route, the code would be like this (also in meteorpad):
WebApp.connectHandlers.use(
'/test',
Meteor.bindEnvironment(function(req, res, next) {
var someSyncData = Players.findOne();
res.write(JSON.stringify(someSyncData));
res.end();
})
);
除非您试图同时将多个异步事件发送到 运行,否则您无需自己使用纤程或承诺。
为了处理文件读取或其他尚未同步的功能,Meteor 还提供Meteor.wrapAsync()
使它们同步。
还有 packages and a help page 可以为您提供其他高级选择。
我通常使用 Meteor (1.0.3),但对于一个特殊情况,我使用原始服务器端路由来呈现文件——所以我在 Meteor 方法之外。
我也在使用 node fs.writeFile/fs.readFile 和 exec 命令调用 Linux 命令行实用程序。
我提出这个问题的唯一一点是节点调用当然是异步的。因此,我选择使用节点 Q 库来管理异步回调。
这一切都有效,直到我添加了一行来调用 MongoDB 数据库。
这样的调用:
var record_name = Mongo_Collection_Name.findOne({_personId: userId}, {fields: {'_id': 0}});
产生以下错误:
[Error: Can't wait without a fiber]
只有当我将函数包装在 Promise 中时才会出现此错误。
例如,像这样的东西会抛出:
getRecordExample = function () {
var deferred = Q.defer();
var record_name = Mongo_Collection_Name.findOne({_personId: userId}, {fields: {'_id': 0}});
// do something
// if no error
deferred.resolve(record_name);
return deferred.promise;
}
如果我使用 Meteor Fibers 库,我不会收到错误消息:
getRecordExample = function () {
var deferred = Q.defer();
Fiber = Npm.require('fibers');
var record_name
Fiber(function () {
record_name = Mongo_Collection_Name.findOne({_personId: userId});
}).run()
// do something
// if no error
deferred.resolve(record_name);
return deferred.promise;
}
但是,record_name 变量在 fiber 之外是未定义的,所以据我所知,我没有办法将变量传递到 Fiber 范围之外。
一个更精确的例子
这有点长,所以您必须向下滚动才能看到全部内容。我基本上是在这里构建一个工作流,所以有流程和子流程。
// both/routes.js
Router.route('/get-route', function(req, res) {
// get the userId then start the workflow below
// using Promises here because these were firing concurrently
Q(userId)
.then(process_1)
.then(process_2)
.done();
}, { name: 'server-side-ir-route', where: 'server' }
// server.js
process_1 = function (userId) {
sub_process_1(userId);
sub_process_2(userId);
return userId;
}
process_2 = function (userId) {
sub_process_3(userId);
sub_process_4(userId);
return userId;
}
sub_process_1 = function (userId) {
var result = get_record_1(userId);
// do stuff with result
// using Q library to call out to async fs.writeFile, return Promise
fs_writeFile_promise(result)
.catch(function (error) {
console.log('error in sub_process_1_write', error);
})
.done(function () {
console.log('done with sub_process_1');
}
return userId;
}.future() // <-- if no future() here, the exception is thrown.
sub_process_2 = function (userId) {
var result = get_record_2(userId);
// do stuff with result
// using Q library to call out to async fs.writeFile, return Promise
fs_writeFile_promise(result)
.catch(function (error) {
console.log('error in sub_process_1_write', error);
})
.done(function () {
console.log('done with sub_process_1');
}
return userId;
}.future()
// async because of I/O operation (I think)
get_record_1 = function (userId) {
var record_1 = Mongo_Collection_Name.findOne({'userId': userId});
// do stuff
return record_1;
}
get_record_2 = function (userId) {
var record_2 = Mongo_Collection_Name.findOne({'userId': userId});
// do stuff
return record_2;
}
// async operation using Q library to return a Promise
fs_writeFile_promise = function (obj) {
var deferred = Q.defer();
fs.writeFile(obj.file, obj.datas, function (err, result) {
if (err) deferred.reject(err);
else deferred.resolve('write data completed');
});
return deferred.promise;
}
现在,让我们假设 process_2 函数与 process_1
完全一样此外,我们应该假设我在每个函数中都有 console.log('step_start') 和 console.log('step_end')。这是它在命令行中的样子:
- 启动进程
- 结束进程
- 启动进程 1
- 结束进程 1
- 启动进程 2
- 启动子进程 1
- 正在获取记录 1
- 启动子进程 2
- 正在获取记录 2
- 返回记录 1
- 结束子进程 1
- 在子进程 1 中调用了 writeData
- 返回记录 2
- 在子进程 2 中调用了 writeData
- 结束进程 2
- 结束子流程 1
我必须在 sub_process_1() 函数上放置一个 Fiber (future) 的原因是,当我将函数 process_1() 放置在顶部的 Q 链中时,我得到了错误:没有光纤就等不及了。
如果我删除顶部 Q 链中的 process_1() 并从 sub_process_1() 中删除 .future() 则不会引发异常。
问题
- 为什么调用 Promise 中的 Mongo 集合会导致 Meteor 应用程序中的光纤错误?
- 在同步函数中调用异步函数通常会导致同步函数变成异步函数吗?
- 如何解决这个问题?
解决此问题的最常见方法是将使用 Meteor 函数的异步回调包装在 Meteor.bindEnvironment()
.
如果您使用的是 Meteor 内核 WebApp package to handle your server side route, the code would be like this (also in meteorpad):
WebApp.connectHandlers.use(
'/test',
Meteor.bindEnvironment(function(req, res, next) {
var someSyncData = Players.findOne();
res.write(JSON.stringify(someSyncData));
res.end();
})
);
除非您试图同时将多个异步事件发送到 运行,否则您无需自己使用纤程或承诺。
为了处理文件读取或其他尚未同步的功能,Meteor 还提供Meteor.wrapAsync()
使它们同步。
还有 packages and a help page 可以为您提供其他高级选择。