javascript基础系列之迭代器与生成器

144 阅读3分钟

迭代器是一种特殊的对象

特点:1)对象都有next()方法;2)调用next方法后返回一个对象;3)返回的对象有done和value属性,done为boolon类型,value为下一个将要返回的值

function createIterator(items) {
    var i = 0
    return {
        next: function () {
            var done = (i >= items.length)
            var value = !done ? items[i++] : undefined;
            return {
                done: done,
                value: value
            }
        }
    }
}

var iterator = createIterator([1, 2, 3, 4])

console.log(iterator.next()) //1
console.log(iterator.next()) //2
console.log(iterator.next()) //3
console.log(iterator.next()) //4

生成器:一种返回迭代器的函数

function* createIterator_() {
    yield 1
    yield 2
    yield 3
}
let iterator1 = createIterator_()

console.log(iterator1.next().value)
console.log(iterator1.next().value)
console.log(iterator1.next().value)
console.log(iterator1.next().value) //undefined

迭代消息传递

  1. next()方法返回的对象中的value值为生成器中yield语句返回的值
  2. next()方法中若传入值,那么该值会改变上一次yield语句的返回值
  3. next()与yield语句不匹配,N个yield语句对应N+1次next()方法调用
function* createIterator() {
    //next 内部传入的值会替代上一次yield的返回值 但是首次传入的时候
    //并没有-1次传来的值 因此 无论传递什么都会被丢弃
    let first = yield 1          
    // 上一次返回的值是 1 赋值给first 
    let second = yield first + 2    
    // 上一次返回的值是 first + 2 赋值给second 
    let third = yield second + 3   
}
let ex1 = createIterator()

//第一次无论传多少值都被丢弃
console.log(ex1.next(8))    //1
console.log(ex1.next(4))    //6
console.log(ex1.next(5))    //8
console.log(ex1.next(6))    //undefined, N+1次调用next()

迭代器模式的异步任务

示例:生成器在yield处暂停,本质上是提出一个问题:“我该返回什么值来赋给变量text”

在本示例中,我们把异步函数foo抽象了出去,在生成器中忽略了异步的细节,让我们以一种同步的形式来追踪整个流程:“1. foo函数发起网络请求; 2.获得返回的数据再处理”

function foo(x, y){
    ajax("http://sample.url/?x=" + x + "&y=" + y,
    function(err, data){
        if(err){
            it.throw(err)
        }else{
            //利用next改变上一次yiled的返回值
            it.next(data)
        }
    })
}

function *main(){
    try{
        const text = yield foo(11, 31)//it.next(data)改变了yield表达式返回值
        console.log(text)
    }catch(err){
        console.log(err)
    }
}

let it = main()
it.next()//首次启动

此外,还有上述man方法中利用try...catch可以捕获异步函数调用过程中产生的错误。 这在promise中是无法实现的。

function *main(){
    let x = yield "Hello world"
    yield x.toLowerCase()
}
let it = main()
it.next().value//Hello world

//版本一:捕获yield表达式运行错误
try{
    it.next(42)// x.toLowerCase() 触发了异常
}catch(err){
    console.log(err)//此处捕获了错误
}

//版本二:手动抛出错误
try{
    it.throw('err')
}catch(err){
    console.log(err)//err
}

终极版本

纯生成器

function run(generate) {
    let task = generate()
    //在这里task第一次迭代
    let result = task.next()
    function step() {
        if (!result.done) {
            const value = result.value
            if (typeof value == 'function') {
                //这里传入的是callback
                value((err, data) => {
                    if (err) {
                        result = task.throw(err)
                        return
                    } else {
                        result = task.next(data)
                        step()
                    }
                })
            } else {
                result = task.next(value)
            }
        }
    }
    //step函数第一次运行的时候task第二次迭代
    step()
}

//使用范例
const readFile = filename =>{
    return callback =>{
        //currying,callback在迭代器中传入
        fs.readFile(filename, callback)
    }
}

run(function* (){
    let contents = yield readFile("example.txt")//yield右边是第一次迭代的结果 迭代器对象 value是函数
    console.log(contents)                       //此处ontents变成了第二次迭代过程中next(data)中的data
})

迭代器+Promise

实例1:thunk

function run(generate) {
    let task = generate()
    let result = task.next()
    function step() {
        if (!result.done) {
            //promise实例中this.data = result.value
            let promise = Promise.resolve(reslut.value)
            promise.then(value => {
                result = task.next(value)
                step()
            }.catch(error=>{
                result = task.throw(error)
                step()
            })
        }
    }
    step()
}

//使用范例
const readFile = filename =>{
    return new Promise((resolve, reject)=>{
        fs.readFile(filename, (err, data)=>{
            if(err){
                reject(err)
            }else{
                resolve(data)
            }
        })
    })
}

run(function* (){
    try{
        let contents = yield readFile("example.txt")
        console.log(contents)  
    }catch(err){
        console.log(err)
    }
})

实例2:

个人更喜欢第一个例子,更容易理解,而且不管怎么都要将生成器内部的异步函数抽象出来。

function run(gen) {
    let args = [].slice.call(arguments, 1)
    let it = gen.apply(this, args)
    return Promise.resolve().then(function handleNext(value) {
        //第一次迭代next()方法的时候 value为undefined,第一次调用不过无所谓什么值
        var next = it.next(value)
        return (function handleResult(next) {
            //当迭代器出现错误的时候 done会变成true
            if (next.done) {
                //返回一个值
                return next.value
            } else {
                //在此处巧妙地next.value传入回调函数handleNext,
                return Promise.resolve(next.value)
                    .then(handleNext, function handleErr(err) {
                        //如果运行出错,就把错误传回生成器
                        return Promise.resolve(it.throw(err))
                            .then(handleResult) //handleResult(it.throw(err))
                    })
            }
        })(next)
    })
}