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

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

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

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

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

    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实现的

    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


    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的状态 不管 反正先写了再说

    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标准