Web Js 异步编程理解(1)
异步
自己写的帮助理解的 fiddle: https://jsfiddle.net/anribras/brkyxmo5/24/
阮一峰的文章: http://www.ruanyifeng.com/blog/2015/04/generator.html
js 异步的核心是用近似
同步的方式写链式的异步
.
要理解,异步不是指多个任务一起执行,那叫多线程.
区分同步和异步:
同步: A----等待----B
异步: A----另外的任务C----B
callback hell 和 Promise
calllback hell 是异步回调里再异步回调.
Promise 用 resovel/reject/then 的方式,让链式异步回调变得更友好,但是,Promise 仍然存在缺陷,它只是让嵌套变得更加好看,(对大多数人其实就够了…),并没有解决问题.
somOp.then( return new Promise((resolve,reject)=>{...}))
.then( return new Promise((resolve,reject)=>{...})
.then(...)
真正的异步解决方案是 async/await.先看生成器
.
生成器
接触这个概念是在 python 里面,如 yield,next
. js 里也是类似的:
//模拟异步回调
function asyncOp(t, cb) {
setTimeout(() => cb(), t);
}
let a = Array(10);
function* gen(n) {
dump("in gen");
while (n > 0) {
dump(n);
//yield n*n;
a[n] = yield asyncOp(n * 100, () => dump("async op:" + n));
console.log(a[n]);
n--;
}
return 0;
}
g = gen(10);
v = g.next(100).value; //undefined
res = g.next(v);
g.next();
gen 叫 Generator,yield 后面的表达式将先不执行,需手动 next()来运行.
next().value是yield后表达式的返回值.
next().done 表示Generator后面是否还能继续执行
注意g.next(v)
. v 是上 1 个 next().value,作为给当前 next 的参数,next 的参数将作为整个yield+表达式的返回值
. 区分上面说的yield后的表达式的返回值
.最终效果是a[n] = v
.
这里的 v 是返回的 asycnOp 的函数结果,也就是 undefined.
再来看 3 种方式对调用者的区别:
- callback 的 Hell
func1( ()=>{
func2(()=>{
func3()=>{ ...}
})
})
- Promise 的链式
someOp.then( return new Promise((resolve,reject)=>{...}))
.then( return new Promise((resolve,reject)=>{...})
.then(...)
- Generator 函数
function* func() {
let v = yield func1();
let v = yield func2();
let v = yield func3();
return v;
}
let g = func();
let done = g.next().done;
let v = g.next().value;
while (!done) {
//next参数可作为上个阶段异步任务的返回结果
//具体要不要用,要看实际代码怎么写
let t = g.next(v);
done = t.done;
v = v.value;
}
上面的代码并没有少多少,看上去也不够简洁.
需要 1 个函数把上面的推动 next 执行的构成封装,即自动生成器
,就是自动执行 next,完成整个过程用的.
如果上面的 v 是 1 个闭包,才有 callback,那么考虑在 callback 里推动 next,整个执行就串联起来了.
这就是 trunkify 函数,就是把带 callback 的函数变为
func(a,b, function(res){...})
let trunkyFunc = trunkify(func);
//func的调用就变成了
trunkyFunc(a,b)( callback);
Thunk 函数本质就是闭包。它真正的威力,在于可以简化自动执行 Generator 函数的实现:
//生成器
var gen = function*() {
var r1 = yield trunkyFunc("/etc/fstab");
console.log(r1.toString());
var r2 = yield trunkyFunc("/etc/shells");
console.log(r2.toString());
};
//自动指定函数,封装next()执行
function run(fn) {
let gen = fn();
function next(err, data) {
//1 将异步的结果传给next,作为yield+表达式的返回. 异步执行的结果,被同步的方式获得.
let result = gen.next(data);
if (result.done) return;
//result.value如果是1个trunky函数,这里才可以这么实现
//2 回调里继续next(),推动执行,所有的yield,所有的异步操作,同步执行.
result.value(next);
}
next();
}
run(gen);
async await
该 async/await 出场了,把上面的用 Promise 来实现.
aysnc func() {
let v1 = await func1();
let v2 = await func2();
}
aysnc 到底怎么封装的?
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function*() {
// ...
});
}
function spawn(genF) {
//async函数的返回一定是Promise
return new Promise(function(resolve, reject) {
//执行generator函数
var gen = genF();
function step(nextF) {
try {
//2 6
var next = nextF();
} catch (e) {
//如果结束或者错误,通过Promise reject出来
return reject(e);
}
//3 7
if (next.done) {
//8
//最终async函数返回的Promise的.then
return resolve(next.value);
}
//4
//await后的表达式的值可以是Promise,也可以不是,
//不是则立即执行
//是的话,则是异步的
Promise.resolve(next.value).then(
function(v) {
//5
//这一步将异步结果返回给res; (res=await xxx)
step(function() {
return gen.next(v);
});
},
function(e) {
step(function() {
return gen.throw(e);
});
}
);
}
// 1
step(function() {
return gen.next(undefined);
});
});
}
核心的推动效果:
function step(f) {
f();
Promise.resovle(next.value).then {
step(()=>gen.next())
}
}
//step里的f就是next()的执行
step(gen.next());
再简化点,就是个很简单的递归.
gen = genF();
function step(f) {
f();
if (xxx) return;
step(() => gen.next());
}
step(() => gen.next());
异步变同步
aysnc func() {
let v1 = await func1();
let v2 = await func2();
}
如果 func1,func2 可以并发,await 变成同步后,实际是浪费了.
可以用 Promise.all:
async function() {
urls = ['xx1', 'xxx2','xxx3'];
await Promise.all( urls.map( (async (url)=> {
let img = await getImage(url);
return await saveImage(img;)
})))
}