异步

自己写的帮助理解的 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;)
  })))
}