在阅读javascript博客和文章时,我看到了很多ES6 Generators的兴趣,但我不明白它们与用一系列函数组成的当前序列的本质区别。例如,下面的工厂将采取一系列功能步骤并在步骤之间产生。
function fakeGen(funcList) {
var i = 0, context;
return function next() {
if (i<funcList.lenght) {
return {value: funcList[i++](context)}
} else return {done:true}
}
}
我错过了哪些好处以及如何在ES6中实现魔法转换?
答案 0 :(得分:2)
生成器本质上是一个枚举器函数,它允许在调用它时更改您正在操作的上下文,实际上它与您的函数数组之间没有太大的区别,但是你的优点是get是它不必是被评估函数内部的函数,简化了闭包。请看以下示例:
function* myGenerator() {
for (var i = 0; i < arr.length; i++) {
yield arr[i];
}
}
这是一个非常简单的示例,但不是必须构建您需要提供的上下文,以便为某人列举结果,而是为您提供,并确保您done
在完成之前,属性将为false。此功能看起来比您给出的示例更清晰。可能最大的优势在于围绕此优化可以在引擎盖下进行,因此优化了对象内存占用。
一个很好的解决方法是在枚举多个对象集合时真正清理代码,如下所示:
function* myGenerator() {
for (var i = 0; i < arr.length; i++) {
yield arr[i];
}
for (var i = 0; i < arr2.length; i++) {
yield arr2[i];
}
yield* myGenerator2();
}
使用链式嵌套函数执行此操作可以完成相同的任务,但代码的可维护性和可读性会受到一定程度的影响。
对于转发器来说,来自CS线程:
没有冲突。 Coffeescript将生成所需的任何javascript,以编译它使用的任何语法,旧的或新的 过去,在所有浏览器都支持之前,coffeescript不会使用任何javascript功能。这也可能适用于发电机。在此之前,您需要使用反引号。
我对大多数转录程序的一般理解是,在实现不会遍历并且通常兼容的功能时,他们必须要小心,因此通常在游戏后期。
就像你说的那样,生成器没有做任何超级特殊的事情,它只是语法糖,使编码更容易阅读,维护,消费或表现更好。
答案 1 :(得分:2)
@tophallen是对的。您可以完全在ES3 / ES5中实现相同的功能。但语法不一样。让我们举一个例子,希望能解释为什么语法很重要。
ES6生成器的主要应用之一是异步操作。有several runners用于包装生成Promises序列的生成器。当一个包装的生成器产生一个promise时,这些runners会等到Promise被解析或拒绝,然后恢复生成器,传回结果或使用iterator.throw()
在yield点抛出异常。
一些跑步者,比如tj/co,还允许产生一系列承诺,传回值数组。
这是一个例子。此函数并行执行两个url请求,然后将其结果解析为JSON,以某种方式将它们组合,将组合数据发送到其他URL,并返回(答应)答案:
var createSmth = co.wrap(function*(id) {
var results = yield [
request.get('http://some.url/' + id),
request.get('http://other.url/' + id)
];
var jsons = results.map(JSON.parse),
entity = { x: jsons[0].meta, y: jsons[1].data };
var answer = yield request.post('http://third.url/' + id, JSON.stringify(entity));
return { entity: entity, answer: JSON.parse(answer) };
});
createSmth('123').then(consumeResult).catch(handleError);
请注意,此代码几乎不包含样板文件。大多数行执行上述描述中存在的某些操作。
还要注意缺少错误处理代码。所有错误,包括同步(如JSON解析错误)和异步(如失败的url请求)都会自动处理,并拒绝生成的承诺。
如果您需要从某些错误中恢复(即阻止它们拒绝生成的Promise),或者使它们更具体,那么您可以使用try..catch
围绕生成器中的任何代码块,并且同步并且异步错误将在catch
块中结束。
使用一系列函数和一些辅助库(如async)可以明确地实现它:
var createSmth = function(id, cb) {
var entity;
async.series([
function(cb) {
async.parallel([
function(cb){ request.get('http://some.url/' + id, cb) },
function(cb){ request.get('http://other.url/' + id, cb) }
], cb);
},
function(results, cb) {
var jsons = results.map(JSON.parse);
entity = { x: jsons[0].meta, y: jsons[1].data };
request.post('http://third.url/' + id, JSON.stringify(entity), cb);
},
function(answer, cb) {
cb(null, { entity: entity, answer: JSON.parse(answer) });
}
], cb);
};
createSmth('123', function(err, answer) {
if (err)
return handleError(err);
consumeResult(answer);
});
但这真的很难看。更好的想法是使用承诺:
var createSmth = function(id) {
var entity;
return Promise.all([
request.get('http://some.url/' + id),
request.get('http://other.url/' + id)
])
.then(function(results) {
var jsons = results.map(JSON.parse);
entity = { x: jsons[0].meta, y: jsons[1].data };
return request.post('http://third.url/' + id, JSON.stringify(entity));
})
.then(function(answer) {
return { entity: entity, answer: JSON.parse(answer) };
});
};
createSmth('123').then(consumeResult).catch(handleError);
比使用生成器的版本更短,更干净,但代码更多。还有一些样板代码。请注意这些.then(function(...) {
行和var entity
声明:它们不执行任何有意义的操作。
较少的样板(=生成器)使您的代码更易于理解和修改,并且编写起来更有趣。这些是任何代码最重要的特征之一。这就是为什么许多人,特别是习惯于其他语言的类似概念的人,对发电机如此欣喜若狂:)
关于你的第二个问题:转发器使用闭包,switch
语句和状态对象来完成他们的魔术。例如,这个函数:
function* f() {
var a = yield 'x';
var b = yield 'y';
}
将由regenerator转换为此值(Traceur的输出看起来非常相似):
var f = regeneratorRuntime.mark(function f() {
var a, b;
return regeneratorRuntime.wrap(function f$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
context$1$0.next = 2;
return "x";
case 2:
a = context$1$0.sent;
context$1$0.next = 5;
return "y";
case 5:
b = context$1$0.sent;
case 6:
case "end":
return context$1$0.stop();
}
}, f, this);
});
正如你所看到的,这里没什么神奇之处,最终的ES5相当微不足道。真正的魔力在于生成ES5的代码,即在转换器的代码中,因为它们需要支持所有可能的边缘情况。并且最好以产生高性能输出代码的方式执行此操作。
UPD :here is an interesting article可追溯到2000年,描述了普通C中伪协同程序的实现:) Regenerator和其他ES6的技术&gt; ES5转发器用于捕获发生器的状态非常相似。