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}