从pyhton的生成器到js的生成器

说起这个generator啊 本不是js的东西 而是从其他语言中借鉴来的 当初初es6标准的时候想必大家都看过了 也都知道所谓生成器 但是估计大家和我一样 可能从来也没用过吧 最大的用处估计就是在async/await实现之前来替代的处理异步吧 比如koajs1.x 但是这并不是generator本身应该的正确用法 如果你是generator 你心里会怎么想 本来我估计也用不到这东西 但是最近我不是后来去写了python嘛 也终于在一次认识到了这个东西 然后再次从js的角度看待generator

首先来个例子 redis中每个用户有一个物品列表list user:$userId:items 每个物品有物品详情hashset item:$itemId 现在需要遍历所有物品 为了减轻内存负担 所以要一条一条取

js 回调参数 内部回调函数版

1
2
3
4
5
6
7
8
9
10
11
12
function getAllItem(userId, callback){
redis.lrange(`user:${userId}:items`, 0, -1, function(err, itemIds){
if (err) return callback(err);
for (let itemId in itemIds){
redis.hgetAll(`item:${itemId}`, callback);
}
});
}

getAllItem(1234, function(itemDetail){
console.log(itemDetail);
});

这个版本因为整个函数都是异步的 获取所有物品后再执行后面的过程 这个操作是没法处理了 需要对回调参数函数进行再次封装 即使内部换成Promise也是一样的

为了实现这个 再写一个内部用async/await实现的

1
2
3
4
5
6
7
8
9
10
11
12
async function getAllItem(userId, callback){
const itemIds = await redis.lrange(`user:${userId}:items`, 0, -1);
for (let itemId in itemIds){
const itemDetail = await redis.hgetAll(`item:${itemId}`);
callback(itemDetail);
}
}

await getAllItem(1234, function(itemDetail){
console.log(itemDetail);
});
// TODO

这个实现解决了上面提到的那个问题 但是目前async/await支持度不高

好 咱来看看 python的版本 python当然也有回调函数啊 但是python的回调函数难用的一笔 写出来的效果和上面那个差不多 主流的方法还是使用yield

1
2
3
4
5
6
7
8

def getAllItem(userId):
itemIds = redis.lrange("user:%s:items" % userId, 0, -1)
for itemId in itemIds:
yield redis.hgetAll("item:%s" % itemId)

for itemDetail in getAllItem(1234):
print itemDetail

卧槽 这简直甩js几条街了 是不是 但是js也有yield的啊 但是光是js的generator写不出这样的代码诶 尤其是层层嵌套回调函数要使用 yield简直是不可能

咱来写一个js的generator版的 使用async generator以及for-await-of 这两个目前还没上标准 还是stage 3的状态 不管 反正先写了再说

1
2
3
4
5
6
7
8
9
10
async function* getAllItem(userId){
const itemIds = await redis.lrange(`user:${userId}:items`, 0, -1);
for (let itemId in itemIds){
yield redis.hgetAll(`item:${itemId}`);
}
}

for await(let itemDetail in getAllItem(1234)){
console.log(itemDetail)
}

卧槽 这个是不是和python那个yield版本一模一样

我觉得这才是generator真正的用法 从语义上最好的当然是使用async generator 或者 async iterator 不支持异步的迭代器或者生成器都是没实际价值的

但是就这个需求目前来看 async/await+参数回调函数是目前最好的解决办法 因为异步迭代器还没有人支持啊 目前来看 异步迭代器 asynchronous-iteration最快可能进入ES 2018标准