es6

详解generator(三)——处理thunk

2018-05-09  本文已影响36人  mytac

thunk

js中的thunk是指一个用于调用另外一个函数的函数,没有任何参数。也可以说,使用一个函数定义封装函数调用,包括需要的任何参数,来定义这个调用的执行,那么这个封装函数就是一个thunk。如:

function foo(x,y){
    return x+y
}

function bar(){
    foo(1,2)
}

console.log(bar()) // 3

以上,同步的thunk是非常简单的。

异步thunk

把狭窄的thunk定义扩展到包含接受一个回调。

function bar(a,b,cb){
    setTimeout(function(){
        cb(a+b)
    },1000)
}

function barThunk(cb){
    bar(1,2,cb)
}

barThunk((a)=>{console.log(a)}) // 3

thunkify

如上所见,barThunk只需一个传入回调函数参数cb,因为它已经预先有指定值1和2可以传给bar。thunk只需等待回调完成。

但是我们并不会给每个函数都写一个他自己的thunk函数,再在这个函数中去调用原函数,所以来封装一个thunkify工具来完成这项工作。

function thunkify(fn){
    const args=[].slice.call(arguments,1)
    return function(cb){
        args.push(cb)
        fn.apply(null,args)
    }
}

const barThunk=thunkify(bar,1,2)

barThunk((sum)=>{console.log(sum)})

然而,以上方式实现thunkify需要传入bar()函数和其他参数,每次调用这个thunkify需要:

const barThunk1=thunkify(bar,1,2)
const barThunk2=thunkify(bar,3,4)

barThunk1((sum)=>{console.log(sum)})
barThunk2((sum)=>{console.log(sum)})

是不是略显臃肿,且不灵活;我们再把thunkify重构一下,使其构造一个工厂函数thunkory(thunk + factory),再由thunkory生成thunk。

function thunkify(fn) {
    return function() {
        const args = [].slice.call(arguments)
        return function(cb) {
            args.push(cb)
            fn.apply(null, args)
        }
    }
}

const barThunkory=thunkify(bar)

const barThunk1=barThunkory(1,2)
const barThunk2=barThunkory(3,4)

barThunk1((sum)=>{console.log(sum)})
barThunk2((sum)=>{console.log(sum)})

----
3 7 同时输出

promise/thunk

thunk和promise的特性并不相同,promise要比裸thunk功能更强、更值得信任。

但从另一个角度来说,他们都可以被看作是对一个值的请求,回答值可能是异步的。

thunkory产生的函数是thunkify;thunkify产生thunk;而promisory产生promisify,接着产生promise。所以thunkory和promisory是完全对称的。

为了说明这种对称性,改变一下前面的bar()

function bar(a,b,cb){
    setTimeout(function(){
        cb(null,a+b)
    },1000)
}


const barThunkory=thunkify(bar)
    const barPromisory=promisify(bar)

const barThunk=barThunkory(1,2)
const barPromise=barPromisory(1,2)

barThunk1((err, sum) => {
    if (err) {
        console.error(err)
    } else {
        console.log(sum)
    }
})
fooPromise
    .then(sum => {
            console.log(sum)
        },
        err => {
            console.error(err)
        }
    )

thunkory和promisory本质上都在提出一个请求,分别由thunk fooThunk和promise fooPromise表示对这个请求的未来的答复。

我们更改一下上一篇文章中的run函数,使其可以自动yield thunk。

function run(gen) {
    const args = [].slice.call(arguments, 1)
    const it = gen.apply(this, args)

    return Promise.resolve() //创建一个已完成的promise
        .then(function handleNext(value) {
            const next = it.next(value)
            return (function handleResult(next) {
                if (next.done) { // 生成器执行完成
                    return next.value
                }else if(typeof next.value==='function'){
                    return new Promise((resolve,reject)=>{
                        next.value((err,msg)=>{
                            if(err){reject(err)}
                                else{
                                    resolve(msg)
                                }
                        })
                    }).then(handleNext,
                    function handleErr(err) {
                                return Promise.resolve(it.throw(err)).then(handleResult)
                            }
                    )
                } 
                else { // 继续执行
                    return Promise.resolve(next.value).then(handleNext,handleErr)
                }
            })(next)
        })
}

我们尝试run一个含有thunkory的generator:

function *baz(){
    console.log(yield barThunkory(1,2))
    console.log(yield barThunkory(3,4))
}

run(baz)
------
3
7

ES6之前的生成器

对于es6中所有的语法扩展来说,都有工具用于接收es6语法并将其翻译为等价的前ES6代码~

手工变换

在实现transpiler之前,我们先推导下对于生成器来说手工变换是如何实现的。

先写一个generator例子:

function* foo(url) {
    // state1
    try {
    // 这里request是一个支持promise的ajax工具
        console.log('url is ', url)
        const TMP1= request(url)
        
        // state2
        const r = yield TMP1
        console.log(r)
    } catch (err) {
        //state3
        console.error(err)
        return false
    }
}

const it=foo('http://some.url.1')

1.构造迭代器轮廓

我们来构造一个迭代器先把轮廓写出来迭代器包含next和throw方法

function foo(url){
    return {
        next:(v)=>{},
        throw:(e)=>{}
    }
}

const it=foo('http://some.url.1')

2.使用闭包暂停作用域

然后我们来写一下generator的核心功能:暂停代码执行。

1是起始状态,2是request成功后的状态,3是request失败的状态。我们再闭包中定义一个state用于追踪状态:

function foo(url){
    let state;
}

我们在foo中定义一个process函数来对不同的state进行处理~

function foo(url){
    let state,val;
    function process(v){
        switch(state){
            case 1:
            console.log('requesting',url)
            return request(url);
            case 2:
            val=v
            console.log(val)
            return;
            case 3:
            const err=v
            console.error(err)
            return;
        }
    }

}

每次需要处理一个新状态就会调用process。

3.定义迭代器函数的代码

    return {
        next(v){
            if(!state){
                state=1
                return {
                    done:false,
                    value:process()
                }
            }else if(state==1){
                state=2
                return {
                    done:true,
                    value:process(v)
                }
            }else {
                return {
                    done:true,
                    value:undefined
                }
            }
        },
        throw(e){
            if(state==1){
                state=3
                return {
                    done:true,
                    value:process(e)
                }
            }else{
                throw e
            }
        }
    }

调用:

const it=foo('this is url')
it.next()
// requesting this is url
// {done: false, value: Promise}
it.next()
// {done: true, value: undefined}
上一篇下一篇

猜你喜欢

热点阅读