詳解generator(三)——處理thunk

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}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章