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}