一、回调函数
回调函数是异步编程最基本的方法
//一个异步函数,异步任务执行完成后调用回调函数
function asyncFn(text, callback){
let time = 1000 * Math.random();
//执行一些异步操作,然后调用回调函数
setTimeout(()=>{
callback(text, time);
}, time);
}
然后这样调用
asyncFn("done", (text, time)=>{
console.log(text, time);
});
如果要顺序执行一系列这样的任务的话,这样调用
asyncFn("first", (text, time)=>{
asyncFn("second", (text, time)=>{
asyncFn("third", (text, time)=>{
console.log(text, time);
});
console.log(text, time);
});
console.log(text, time);
});
按照上面的写法,当依赖的前置任务比较多的时候,回调就会嵌套得越来越深,一般称其为 “callback hell”
所以要怎么才能写出功能一样,但是有不会变成”callback hell”的代码呢?
二、Thunk
Thunk函数的起源什么的这里就不说了,这里主要说JavaScript 里的 Thunk 函数,简单的来说,Thunk 函数的作用是将多参数函数(参数,回调)变为单参数函数(回调)。
以一开始定义的异步函数来说明
以一开始定义的异步函数来说明
这是一个多参数函数,接受一个text参数,和一个回调参数
//一个异步函数,异步任务执行完成后调用回调函数
function asyncFn(text, callback){
let time = 1000 * Math.random();
//执行一些异步操作,然后调用回调函数
setTimeout(()=>{
callback(text, time);
}, time);
}
这是一个单参数函数,接受一个text参数,然后返回一个只接受回调参数的函数。
本质上就是把原来的异步函数进行了柯里化,把参数和回调函数分离了
//Thunk化的异步函数
function thunkifyAsyncFn(text){
return function (callback){
return asyncFn(text, callback);
};
};
/** 因为只是简单的将参数和最后的回调函数进行分离,
所以就可以很简单得写出一个适用于,最后一个参数为回调函数的异步函数的Thunk转换器
*/
function thunkify(fn) {
return function(...args) {
return function(callback) {
args.push(callback);
return fn(...args);
};
};
};
//将asyncFn 用Thunk转换器转换为Thunk化版本
let thunkifyAsyncFn = thunkify(asyncFn);
得到单参数版本的函数之后,就可以这样调用了
thunkifyAsyncFn("done")((text, time)=>{
console.log(text, time);
});
如果要顺序执行一系列这样的任务的话,这样调用
thunkifyAsyncFn("first")((text, time)=>{
thunkifyAsyncFn("second")((text, time)=>{
thunkifyAsyncFn("third")((text, time)=>{
console.log(text, time);
});
console.log(text, time);
});
console.log(text, time);
});
很显然,这样的写法跟之前的基本没有什么差别,所以 Generator 出现之前,Thunk函数基本没什么用,但是有了 Generator 之后,情况就变得不一样了。
三、Generator
Generator函数执行后会返回一个遍历器,遍历器的的每一个next对应一个yield
function* gen(){
let result = yield "first";
result = yield "second";
result = yield "third";
}
然后这样来调用
let g = gen();
g.next();//{ value: "first", done: false }
g.next();//{ value: "second", done: false }
g.next();//{ value: "third", done: false }
g.next();//{ value: undefined, done: true }
因为是一个遍历器,也可以构造一个循环来自动调用,执行的结果为按顺序在控制台输出3个字符串
function run(gen){
let g = gen();
for( let value of g)
{
console.log(value);
}
}
run(gen);
既然 next() 的返回值中的 value 可以是字符串,当然也可以是函数,那么,之前的Thunk函数就派上用场了
function* gen(){
//将asyncFn 用Thunk转换器转换为Thunk化版本
let thunkifyAsyncFn = thunkify(asyncFn);
//thunkifyAsyncFn执行后返回一个只接受一个回调函数为参数的函数
//即next()的返回值中的value为一个只接受一个回调函数为参数的函数
let [text, time] = yield thunkifyAsyncFn("first");
console.log(text, time);
[text, time] = yield thunkifyAsyncFn("second");
console.log(text, time);
[text, time] = yield thunkifyAsyncFn("third");
console.log(text, time);
}
因为是异步调用,所以之前的run函数中的循环就不适用了,所以要进行一下改造。run函数运行之后,可以看到控制台里的输出循序和之前的嵌套的写法是一致。
function run(gen) {
let g = gen();
function goNext(next) {
if(next.done)
{
return;
}
else
{
//value为一个只接受一个回调函数为参数的函数
next.value((...result)=>{
//next()可以接受一个参数,这个参数就作为上一条 yield 语句的返回值
goNext(g.next(result));
});
}
}
//next()不接受参数,因为为第一条 yield 语句
goNext(g.next());
};
run(gen);
在 Generator 和 Thunk 组合之后,我们终于可以从回调嵌套中解放了出来,只要在需要进行异步调用的地方加上 yield 声明,就可以用同步的形式编写异步的调用了。
既然 Generator 可以和 Thunk 组合,用 Promise 当然也是可以的。
四、Promise
首先把原来的异步函数改成 Promise 的形式
function promiseAsyncFn(text){
return new Promise((resolve, reject)=>{
asyncFn(text, (text, time)=>{
resolve([text, time]);
})
});
}
然后改造一下run函数
function run(gen) {
let g = gen();
function goNext(next) {
if(next.done)
{
return;
}
else
{
//value为一个promise
next.value.then((result)=>{
//next()可以接受一个参数,这个参数就作为上一条 yield 语句的返回值
goNext(g.next(result));
});
}
}
//next()不接受参数,因为为第一条 yield 语句
goNext(g.next());
}
Generator 函数
function* gen(){
// promiseAsyncFn 执行后返回一个promise
//即next()的返回值中的value为一个promise
let [text, time] = yield promiseAsyncFn("first");
console.log(text, time);
[text, time] = yield promiseAsyncFn("second");
console.log(text, time);
[text, time] = yield promiseAsyncFn("third");
console.log(text, time);
}
run(gen)
之后可以看到结果和Thunk化的版本是一样的。
End
没有评论 :
发表评论