請你完成一個 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

相關文章
相關標籤/搜索