请你完成一个 async await 的在较低版本里的实现

1、前言

咱们常常会在编程时遇到异步的需求,异步,就是说一个任务不是能够连续完成,他须要被分红前后执行,先执行一部分,而后程序能够转执行别的任务,待执行完了前一部分,在回头执行后一部分,那么咱们曾经使用的哪些方法实现过呢,这里咱们介绍一下:es6

1.回调函数
2.Promise
3.async await
复制代码

1. 回调函数

举一个例子:咱们对一个文件的读取和修改操做编程

fs.readFile('./ziyi.text', (content) => {
  setTimeout(() => {
    content += '123';
    fs.append('./ziyi.text', content, (err) => {
        ......
    })
  }, 3000)
})

复制代码

这里咱们经过回调函数,能够在读取文件三秒后,进行文件内容的修改,在回调里面又有回调实现异步,可是这就陷入了回调地狱,因此咱们后面异步的实现就出现了链式调用的Promiseapp

2. Promise

Promise('./ziyi.text')
.then(() => {
  content += '123';
})
.then(() => {
  fs.append('./ziyi.text')
})
.then()
......
复制代码

这样链式调用看上去咱们的代码就像串联同样,再也不是多个回调函数嵌套时,Promise虽然跳出了异步嵌套的怪圈,用链式表达更加清晰,可是咱们也发现若是有大量的异步请求的时候,流程复杂的状况下,会发现充满了屏幕的then,看起来很是吃力,而ES7的Async/Await的出现就是为了解决这种复杂的状况。。异步

3. async

async () => {
  let c = await fs.readFile('./ziyi.text')
  c += '123';
  let res = await fs.append('./ziyi.text', c);
}

复制代码

这样咱们的代码阅读起来就很清晰了,每一步作的事都简介明了,其实,在Promise到async以前,做者TJ写了一的Co的库优化了Promise写法,这个优化使用了es6里Generator,大概的写法以下:yield表示暂停,看上去是否是和咱们如今用的async await很像async

co(
  function * test() {
    let c = yield fs.readFile('./ziyi.text');
    c += '123';
    let res = yield fs.append('./ziyi.text', c);
  }
)

复制代码

2、 Generator(生成器)

generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但能够返回屡次。函数

以前的Promise恢复了异步回调的可信任性,而generator正是以一种看似顺序、同步的方式实现了异步控制流程,加强了代码可读性。优化

generator和函数不一样的是,generator由function*定义,而且,除了return语句,还能够用yield返回屡次。它的执行由next()方法来一步一步执行ui

举个例子:spa

1. 手动一步一步执行

function foo() {
  // setTimeout(() => {
  // console.log(1);
  // }, 2000)
  // return 'foo';
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(123);
    }, 2000)
  })
}
function* test() {      //generator
      console.log('start');
      let a = yield foo();  // 赋值语句从右往左<——,因此执行到foo()会中止
      
      console.log('middle')
      let b = yield 2;
      
      console.log('end');
    }
    let g = test();
    console.log(g)
    console.log(g.next());  //执行第一部分,代码开始到第一个yield
复制代码

let g = test(); 咱们先把text()执行结果拿到,在它上面由next()方法执行generator里的代码

g.next() 经过第一次调用next,咱们执行到了foo(),g.next()返回的对象key由value和done,value为yield 后面的内容,done表示是否还有next能够执行,这里done为false,则generator未执行完3d

function* test() {
      console.log('start');
      let a = yield foo(); 
      console.log('a', a);
      let b = yield 2;
      console.log('b', b);
      console.log('end');
    }
    let g = test();
    console.log(g)
    console.log(g.next());
    console.log(g.next());
    console.log(g.next());
复制代码

同理,咱们把后面两步也执行,这时咱们的generator就执行完了。

可是咱们的a,b 未被赋到值,generator规定在next()方法传参能够给上一个(注意是上一个)yield返回值,代码以下

console.log(g.next());
console.log(g.next('A_value'));   
console.log(g.next('B_value'));   
复制代码

赋值成功,a、b虽然被赋值了,可是这样的赋值是不对的,咱们应该将yield后面的foo()和2给a,b。

console.log(g.next());
console.log(g.next(foo()));   
console.log(g.next(2));   
复制代码

2. 实现一个方法来自动所有执行

function generateAutoRun(gen) {
  let g = gen();
  function next(value) {
    let res = g.next(value);    //执行next()
    if (res.done) return;   //结束
    next(res.value);    //从第二个next开始,给上一个的yield返回值
  }
  next();
}
generateAutoRun(test);
复制代码

在赋值时,咱们把上一个上一个next()对象的 value值给yield的返回,这样a就返回了foo()执行的结果,b就为2。

介绍完了generator,咱们就可能够用它来实现一下咱们async了

3、 用Generator实现async

咱们定义两个异步的 Promise前后执行

let p1 = new Promise((resolve) => {
  console.log('p1先');
  setTimeout(() => {
    console.log(1);
    resolve(1)
  }, 2000)
})
let p2 = new Promise((resolve) => {
  console.log('p2后');
  setTimeout(() => {
    console.log(2);
    resolve(2)
  }, 1000)
})

function* test() {
  let a = yield p1;
  console.log(a,'a---')
  let b = yield p2;
  console.log(b,('b---'));
}
复制代码

这时咱们就要实现p1,p2的前后执行了

function asyncTogenerate(gen) {
  let g = gen();
  function step(value) {    //递归调用next
    // 处理 yield 返回值问题
    let info = g.next(value);  //一步一步执行p1,p2,并给a,b 赋值p1,p2
    if (info.done) {   //执行完return掉
      return;
    } else {
      // 把 yield 后面的东西(info.value) 直接 resolve,传给下一个step,给a,b赋值
      Promise.resolve(info.value).then((res) => {
        // 下一个 yield 下一个 递归
        step(res); 
      })
    }
  }
  step();  //递归开始
}
asyncTogenerate(test);
复制代码

Promise的then方法,能够保证前后执行,因此咱们上面代码给每个yield返回的值进行Promise包装一下

Promise.resolve(info.value).then((res) => {  //把value包装,使用它的then方法异步执行
复制代码

看结果

正如咱们想要的同样,p1先执行,而后再是p2,输出结果也没问题,由于p1是1秒后执行,p2是2秒后执行,因此先输出2,再输出1。

a为p1返回的1,,b为p2返回的2,赋值也没有错误。

4、 总结

咱们常说什么async/await的出现淘汰了Promise,能够说是大错特错,偏偏相反,上面的例子说明,正由于有了Promise,才有了改良版的async/await,二者是能够相辅相成的

因此咱们想学好async/await,应该先去了解 Promise

相关文章
相关标签/搜索