据我所知,在JS中编写异步代码的未来风格是使用生成器而不是回调。至少,或者尤其是在V8 / Nodejs社区。是对的吗? (但这可能是有争议的,这不是我的主要问题。)
要使用生成器编写异步代码,我找到了一些库:
它们看起来都很相似,而且我不确定它们中的哪一个使用(或者甚至是重要的)。 (但是,这可能再次引起争议,这也不是我的主要问题 - 但我仍然会对任何建议感到高兴。)
(无论如何我只使用纯V8 - 如果这很重要。我没有使用Nodejs,但我在我的自定义C ++应用程序中使用纯V8。但是,我已经有一些节点式元素我的代码,包括我的自定义require()
。)
现在我有一些以回调方式编写的函数X
,它本身用回调参数调用其他异步函数,例如:
function X(v, callback) {
return Y(onGotY);
function onGotY(err, res) {
if(err) return callback(err);
return Z(onGotZ);
}
function onGotZ(err, res, resExtended) {
if(err) return callback(err);
return callback(null, v + res + resExtended);
}
}
我希望将X
转换为生成器,例如我想function* X(v) { ... }
。这看起来怎么样?
答案 0 :(得分:1)
我使用了非常简单的自己的lib,它对我的小型V8环境非常有效,并且因为JS callstack保持不变,所以也很容易调试。为了使其适用于Nodejs或在网络上,它需要进行一些修改。
这里的理由:
对于调试,我们并不真正需要异步代码 - 我们希望在调试器中有一个很好理解的调用堆栈,尤其是。在node-inspector调试器中。 另请注意,到目前为止,所有异步代码都是完全人为的,我们完全同步执行所有内容,通过V8 Microtasks进行模拟。 回调式异步代码在调用堆栈中已经很难理解了。 生成器式异步代码在传统调试器中完全丢失了调用堆栈信息 - 包括与节点检查器一起使用的当前Chrome Beta Developer Tools V8调试器。这就是发电机的性质(一般是协同程序)。调试器的更高版本可能会处理,但今天情况并非如此。我们甚至需要一些特殊的C ++代码来获取信息。示例代码可以在这里找到: https://github.com/bjouhier/galaxy-stack/blob/master/src/galaxy-stack.cc
因此,如果我们想拥有有用的调试器,今天我们就不能使用生成器 - 至少不像通常那样。
我们仍然希望使用生成器样式的异步代码,因为与回调式代码相比,它使代码更具可读性。
我们引入了一个新函数async
来克服这个问题。想象一下以下回调式异步代码:
function doSthX(a, b, callback) { ... }
function doSthY(c, callback) {
doSthX(c/2, 42, function(err, res) {
if(err) return callback(err);
callback(null, c + res);
})
}
现在,使用async
函数和生成器样式代码的代码相同:
function* doSthX(a, b) { ... }
function* doSthY(c) {
var res = yield async(doSthX(c/2, 42));
return c + res;
}
我们可以提供async(iter)
的两个版本:
iter
。这将保持一个理智的调用堆栈,一切都是串行运行。请注意,有一些值得注意的库可用于第二种方法:
https://github.com/visionmedia/co http://taskjs.org/ https://github.com/creationix/gen-run https://github.com/bjouhier/galaxy
目前,我们只是实施第一种方法 - 使调试更容易。 如果我们曾经想要两者兼而有之,我们可以引入调试标志来通过两种实现进行切换。
global.async = async;
function async(iter) {
// must be an iterator
assert(iter.next);
var gotValue;
var sendValue;
while(true) {
var next = iter.next(sendValue);
gotValue = next.value;
if(!next.done) {
// We expect gotValue as a value returned from this function `async`.
assert(gotValue.getResult);
var res = gotValue.getResult();
sendValue = res;
}
if(next.done) break;
}
return {
getResult: function() {
return gotValue;
}
};
}
// Like `async`, but wraps a callback-style function.
global.async_call_cb = async_call_cb;
function async_call_cb(f, thisArg /* , ... */) {
assert(f.apply && f.call);
var args = Array.prototype.slice.call(arguments, 2);
return async((function*() {
var gotCalled = false;
var res;
// This expects that the callback is run on top of the stack.
// We will get this if we always use the wrapped enqueueMicrotask().
// If we have to force this somehow else at some point, we could
// call runMicrotasks() here - or some other waiter function,
// to wait for our callback.
args.push(callback);
f.apply(thisArg, args);
function callback(err, _res) {
assert(!gotCalled);
if(err) throw err;
gotCalled = true;
res = _res;
}
assert(gotCalled);
return res;
})());
}
// get the result synchronously from async
global.sync_from_async = sync_from_async;
function sync_from_async(s) {
assert(s.getResult); // see async()
return s.getResult();
}
// creates a node.js callback-style function from async
global.callback_from_async = callback_from_async;
function callback_from_async(s) {
return function(callback) {
var res;
try { res = sync_from_async(s); }
catch(err) {
return callback(err);
}
return callback(null, res);
};
}
global.sync_get = sync_get;
function sync_get(iter) {
return sync_from_async(async(iter));
}
// this is like in gen-run.
// it's supposed to run the main-function which is expected to be a generator.
// f must be a generator
// returns the result.
global.run = run;
function run(f) {
return sync_get(f());
}
答案 1 :(得分:0)
我认为通过“将X转变为生成器”意味着“将X变成可以产生的东西”,因为这就是这些库的工作方式。
如果你正在使用co
(我推荐),你需要让你的函数返回thunks,这是一个只接受回调的函数。很简单:
function coX (v) {
return function (cb) {
X(v, cb);
}
}
然后只是:
co(function * () {
yield coX('v');
})();