JavaScript的異步實(shí)現(xiàn)

JavaScript語言的執(zhí)行環(huán)境是‘單線程’的(也就是說,執(zhí)行后續(xù)的代碼之前必須完成前面的任務(wù),也就是采用的是阻塞的方式來執(zhí)行代碼)
這個(gè)模式的好處是實(shí)現(xiàn)起來比較簡單,執(zhí)行環(huán)境相對單純一點(diǎn)嗎,缺點(diǎn)就是每一個(gè)任務(wù)的執(zhí)行要耗時(shí)很長的時(shí)間的話,后面的任務(wù)必須要進(jìn)行排隊(duì),這個(gè)過程會拖慢整個(gè)程序的執(zhí)行。常見的瀏覽器長時(shí)間無響應(yīng)(假死),往往就是因?yàn)橐欢?code>JavaScript代碼長時(shí)間運(yùn)行(比如說發(fā)生了死循環(huán)),導(dǎo)致頁面卡死在了這個(gè)地方,其他的任務(wù)無法執(zhí)行。
為了解決我們上面遇到的問題,我們將JavaScript語言將任務(wù)執(zhí)行分為了兩種:同步模式和異步模式
同步模式:就是上面所說的線性的執(zhí)行,等待前面的任務(wù)結(jié)束,才可以開始后面的任務(wù)。
異步模式:就是每一個(gè)任務(wù)有一個(gè)或是多個(gè)回調(diào)函數(shù),前面的一個(gè)任務(wù)結(jié)束后,并不是執(zhí)行后面的任務(wù),而是執(zhí)行回調(diào)函數(shù),后面的任務(wù)可以不用等待前面的任務(wù)結(jié)束,就開始執(zhí)行,總的來說,就是采用了一種非阻塞的方式來實(shí)現(xiàn)代碼的執(zhí)行。

JavaScript異步編程的方法

ES6之前: 回調(diào)函數(shù)、事件監(jiān)聽、Promise對象
ES6Generator函數(shù)(協(xié)程coroutine)
ES7: async和await

回調(diào)函數(shù)

如何實(shí)現(xiàn)回調(diào)函數(shù)的,可以通過定時(shí)器來實(shí)現(xiàn),但是回調(diào)函數(shù)和異步模式并不是完全一致的。
先舉一個(gè)使用定時(shí)器實(shí)現(xiàn)異步操作的例子。

f1();
f2();
function f1(callback){
  setTimeout(function () {
    // f1的任務(wù)代碼
    callback();
  }, 1000);
}
// 執(zhí)行
f1(f2)

上面的例子我們將同步操作編程了異步操作,這個(gè)時(shí)候f1并不會阻塞程序的運(yùn)行,相當(dāng)于先執(zhí)行程序的主要邏輯,將耗時(shí)的操作進(jìn)行延遲操作。
優(yōu)點(diǎn):回調(diào)韓式是異步編程最簡單,最基礎(chǔ)的方法,非常容易部署
缺點(diǎn):不利于代碼的閱讀和維護(hù),各個(gè)部分的代碼是高度的耦合到了一起,流程會很混亂,而且每一個(gè)任務(wù)只能指定一個(gè)回調(diào)函數(shù)。
我們現(xiàn)在來看一個(gè)同步回調(diào)的例子

function A(callback){
    console.log("I am A");
    callback();  //調(diào)用該函數(shù)
}
function B(){
   console.log("I am B");
}
A(B);

那一半在上面情況下我們需要用到異步模式呢?我們往往使用的場景是我們對數(shù)據(jù)得到的順序有嚴(yán)格的要求,舉個(gè)栗子:

//嵌套回調(diào),讀一個(gè)文件后輸出,再讀另一個(gè)文件,注意文件是有序被輸出的,先`text1.txt`后`text2.txt`
var fs = require('fs');

fs.readFile('./text1.txt', 'utf8', function(err, data){
    console.log("text1 file content: " + data);
    fs.readFile('./text2.txt', 'utf8', function(err, data){
        console.log("text2 file content: " + data);
    });
});

在回調(diào)函數(shù)嵌套的不深的情況下,代碼還是比較容易理解和維護(hù)的,但是一旦嵌套的層數(shù)加深,就會出現(xiàn)‘回調(diào)金字塔’的問題。當(dāng)這里面的每一個(gè)回調(diào)函數(shù)都包含了很多的業(yè)務(wù)邏輯的話嗎,整個(gè)代碼就會變的十分的復(fù)雜,維護(hù)這段代碼回事十分痛苦是的事情,這就是‘回調(diào)地獄’

doSomethingAsync1(function(){
      doSomethingAsync2(function(){
          doSomethingAsync3(function(){
              doSomethingAsync4(function(){
                  doSomethingAsync5(function(){
                      // code...
                  });
            });
         });
     });
 });

還有一個(gè)事情,我們預(yù)設(shè)一下下面代碼的執(zhí)行情況是什么:

var fs = require('fs');

try{
    fs.readFile('not_exist_file', 'utf8', function(err, data){
        console.log(data);
    });
}
catch(e){
    console.log("error caught: " + e);
}

在上面的代碼中,我們嘗試分析一下:讀取一個(gè)不存在的文件,這當(dāng)然會引發(fā) 異常,但是現(xiàn)在的結(jié)果是輸出的結(jié)果是:undefined,因?yàn)樽钔鈱拥?code>try/catch語句是無法捕獲這個(gè)異常的,出現(xiàn)這個(gè)現(xiàn)象的原因是:代碼的異步執(zhí)行導(dǎo)致的
問:我說嘛異步代碼回調(diào)函數(shù)中的異常無法被外層的try/catch語句捕獲。
答:異步調(diào)用一般被劃為為兩個(gè)階段,提交請求和處理結(jié)果,這兩個(gè)階段之間有時(shí)間循環(huán)的調(diào)用,他們屬于不同的時(shí)間循環(huán),彼此之間是沒有關(guān)聯(lián)的,所以try/catch語句只能捕獲檔次時(shí)間循環(huán)的異常,對callback無能為力。
一旦我們在異步調(diào)用函數(shù)中扔出一個(gè)異步I/O請求,異步調(diào)用函數(shù)立即返回,此時(shí),這個(gè)異步調(diào)用函數(shù)和這個(gè)異步I/O請求沒有任何關(guān)系。

事件監(jiān)聽(事件發(fā)布/訂閱)

采用事件驅(qū)動模式,任務(wù)的執(zhí)行不取決于代碼的順序,而是取決于某個(gè)事件是否發(fā)生。
監(jiān)聽函數(shù):on,bind,listen,addEventListener,observe
我們以一個(gè)例子來說明,現(xiàn)在為f1綁定一個(gè)事件。

f1.on('done',f2);
//當(dāng)f1發(fā)生done的時(shí)間的時(shí)候,就執(zhí)行f2,然后對f1進(jìn)行改寫
function f1(){
  settimeout(function(){
    f1.trigger('done');
  })
}

f1.trigger('done');表示,執(zhí)行完成之后,立即觸發(fā)done事件,從而開始執(zhí)行f2,這樣的方法的好處是:比較容易理解,一次可以綁定多個(gè)事件,每一個(gè)事件都可以指定多個(gè)回調(diào)函數(shù),而且可以去耦合,有利于實(shí)現(xiàn)模塊化。
缺點(diǎn):整個(gè)程序變成了事件驅(qū)動,運(yùn)行的流程變得十分的不清楚。
事件監(jiān)聽的方法
(1)onclick方法

element.onclick = function(){
  //處理函數(shù)
}

優(yōu)點(diǎn):寫法兼容了主流的瀏覽器
缺點(diǎn):當(dāng)同一個(gè)element元素綁定了多個(gè)事件的時(shí)候,只有最后一個(gè)事件會被添加
(2)attachEvent和addEventListener方法

// IE瀏覽器使用的事件綁定方法,執(zhí)行的順序是1-2-3
element.attachEvent("onclick",handler1);
element.attachEvent("onclick",handler2);
element.attachEvent("onclick",handler3);
//標(biāo)準(zhǔn)addEventListener,執(zhí)行的順序是3-2-1
element.addEvenListener("click",handler1,false);
element.addEvenListener("click",handler2,false);
element.addEvenListener("click",handler3,false);

其中第三個(gè)參數(shù)決定的是執(zhí)行的順序,是一個(gè)布爾值,當(dāng)為false的時(shí)候表示由里向外,true表示由外向內(nèi)(了解即可)

發(fā)布/訂閱

我們假定,存在一個(gè)"信號中心",某個(gè)任務(wù)執(zhí)行完成,就向信號中心"發(fā)布"(publish)一個(gè)信號,其他任務(wù)可以向信號中心"訂閱"(subscribe)這個(gè)信號,從而知道什么時(shí)候自己可以開始執(zhí)行。這就叫做"發(fā)布/訂閱模式"(publish-subscribe pattern),又稱"觀察者模式"(observer pattern),是一種經(jīng)典的設(shè)計(jì)模式。

//發(fā)布和訂閱事件
let evt = document.createEvent("event"); // 創(chuàng)建自定義事件
 evt.initEvent("click", true, true); // 初始化完畢,即發(fā)布
 window.addEventListener("click", function(e){ // 監(jiān)聽事件,即訂閱
     console.log(1);
 });

 window.dispatchEvent(evt); //派發(fā)事件

這樣的設(shè)計(jì)模式可以應(yīng)用到我們進(jìn)行前端驗(yàn)證的時(shí)候,有很多的輸入項(xiàng)需要進(jìn)行驗(yàn)證,我們需要在點(diǎn)擊確定按鈕的時(shí)候,對所有的驗(yàn)證項(xiàng)進(jìn)行一次性的驗(yàn)證。

Promise對象

promise簡單的來說就是一個(gè)容器,里面保存著某個(gè)未來才會結(jié)束的事件(通常是一個(gè)異步操作)
promise的特點(diǎn):
(1)對象的狀態(tài)不受外界影響,Promise對象代表一個(gè)異步操作,有三個(gè)狀態(tài):pending(進(jìn)行中)、fulfilled(已成功)和rejected(已失?。?僅僅是異步操作的結(jié)果可以影響這三個(gè)狀態(tài),其他任何的操作都不可以改變這個(gè)狀態(tài)。
(2)一旦狀態(tài)已經(jīng)發(fā)生了改變,就不會再改變了,任何時(shí)候得到的都是這個(gè)結(jié)果:從pending變?yōu)?code>fulfilled和從pending變?yōu)?code>rejected。只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會再變了,會一直保持這個(gè)結(jié)果。
promise的缺點(diǎn):
(1)無法取消promise,一旦新建就會立即執(zhí)行,沒有辦法在中途進(jìn)行取消
(2)如果不設(shè)置回調(diào)函數(shù)的話,promise內(nèi)部拋出的錯(cuò)誤,不會反映到外部
(3)當(dāng)處于pending狀態(tài)的時(shí)候,無法得知目前進(jìn)展到某一個(gè)階段(是剛剛開始還是快要結(jié)束)
promise的基本用法
我們在最開始需要創(chuàng)建一個(gè)promise實(shí)例

const promise = new Promise (function (resolve,reject){
//同步的代碼
  if(/*異步操作成功*/){
    resolve(value);
  }else{
    reject(error);
  }
});

Promise 構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù),函數(shù)中有兩個(gè)參數(shù)分別是resolvereject。他們是兩個(gè)函數(shù),有JavaScript引擎提供,不需要自己進(jìn)行部署。
resolve:將Promise對象從“未完成”轉(zhuǎn)變成“成功”,在異步操作成功的時(shí)候調(diào)用,將異步操作的結(jié)果,作為參數(shù)傳遞出去。
reject:將Promise對象的狀態(tài)從“未完成”變?yōu)椤笆 ?,在異步操作失敗的時(shí)候進(jìn)行調(diào)用,并將報(bào)的錯(cuò)誤作為參數(shù)傳遞出去.

promise.then(function(value) {
  // success
}, function(error) {
  // failure,可選,可以不寫
});

Promise.prototype.then()
then方法是定義在原型對象Promise.prototype上的,作用就是為Promise實(shí)例添加狀態(tài)改變時(shí)的回調(diào)函數(shù)。
then方法的第一個(gè)參數(shù)是resolved狀態(tài)的回調(diào)函數(shù),第二個(gè)參數(shù)(可選)是rejected狀態(tài)的回調(diào)函數(shù)。then方法返回是一個(gè)新的Promise實(shí)例(不是原來的那個(gè)Promise實(shí)例),這也是我們調(diào)用鏈?zhǔn)椒椒ǖ母疽琅f。

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

上面的代碼使用then方法,依次指定了兩個(gè)回調(diào)函數(shù)。第一個(gè)回調(diào)函數(shù)完成以后,會將返回結(jié)果作為參數(shù),傳入第二個(gè)回調(diào)函數(shù)。這個(gè)和下面的代碼是不一樣的。

getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).then(function funcA(comments) {
  console.log("resolved: ", comments);
}, function funcB(err){
  console.log("rejected: ", err);
});

第一個(gè)then和上面的代碼用法完全相同,不再詳細(xì)描述,但是第二個(gè)then中傳入了兩個(gè)參數(shù),會等待這個(gè)新的Promise對象狀態(tài)發(fā)生變化。如果變?yōu)?code>resolved,就調(diào)用funcA,如果狀態(tài)變?yōu)?code>rejected,就調(diào)用funcB。
Promise.prototype.catch()
Promise.prototype.catch方法等價(jià)于.then(null,reject)的別名,用于指定發(fā)生錯(cuò)誤的時(shí)候的回調(diào)函數(shù)。主要針對處理前一個(gè)回調(diào)函數(shù)運(yùn)行時(shí)發(fā)生的錯(cuò)誤。

  .catch((err) => console.log('rejected', err));

// 等同于,then沒有指定從從“未完成”到“完成”狀態(tài)的處理函數(shù)
p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("rejected:", err));

也可以對指定的promise拋出的錯(cuò)誤進(jìn)行處理

const promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});
//promise拋出了一個(gè)錯(cuò)誤,被catch指定的回調(diào)函數(shù)捕獲了
promise.catch(function(error) {
  console.log(error);
});

//如果 Promise 狀態(tài)已經(jīng)變成resolved,再拋出錯(cuò)誤是無效的。
const promise = new Promise(function(resolve, reject) {
  resolve('ok');
  throw new Error('test');
});
promise
  .then(function(value) { console.log(value) })
  .catch(function(error) { console.log(error) });
// ok

對于promise來說,對錯(cuò)誤的出來,是采用“冒泡機(jī)制”的,就是一旦promise一旦出現(xiàn)了錯(cuò)誤,錯(cuò)誤會一直向后面進(jìn)行傳遞,直到被捕獲到為止。舉一個(gè)例子:

getJSON('/post/1.json').then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // some code
}).catch(function(error) {
  // 處理前面三個(gè)Promise產(chǎn)生的錯(cuò)誤
});

跟傳統(tǒng)的try/catch代碼塊不同的是,如果沒有使用catch方法指定錯(cuò)誤處理的回調(diào)函數(shù),Promise對象拋出的錯(cuò)誤不會傳遞到外層代碼,即不會有任何反應(yīng)。也就是在Promise中出現(xiàn)錯(cuò)誤的時(shí)候,不會終止程序,而是外部的代碼繼續(xù)執(zhí)行。在報(bào)錯(cuò)后2s還是會打出123,通俗的說就是Promise會吃到錯(cuò)誤。catch方法返回的還是一個(gè)Promise對象,因此后面還可以接著調(diào)用then方法。

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行會報(bào)錯(cuò),因?yàn)閤沒有聲明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  console.log('everything is great');
});

setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123

Promise.prototype.finally()

finally方法用于指定不管 Promise對象最后狀態(tài)如何,都會執(zhí)行的操作。finally方法的回調(diào)函數(shù)不接受任何參數(shù),這意味著沒有辦法知道,前面的Promise 狀態(tài)到底是fulfilled還是rejected。

promise.finally(() => {
  // 語句
});

// 等同于
promise
.then(
  result => {
    // 語句
    return result;
  },
  error => {
    // 語句
    throw error;
  }

Promise.all()

Promise.all方法用于將多個(gè)Promise 實(shí)例,包裝成一個(gè)新的 Promise 實(shí)例。Promise.all方法接受一個(gè)數(shù)組作為參數(shù),p1、p2、p3都是 Promise實(shí)例,p的狀態(tài)由P1,P2,P3決定。
(1)當(dāng)P1,P2,P3狀態(tài)都變成fulfilled,p的狀態(tài)才會轉(zhuǎn)換成fulfilled,此時(shí)P1,P2,P3的返回值組成一個(gè)數(shù)組返回給p。
(2)只要p1、p2、p3之中有一個(gè)被rejected,p的狀態(tài)就變成rejected,此時(shí)第一個(gè)被reject的實(shí)例的返回值,會傳遞給p的回調(diào)函數(shù)。

var fs = require('fs')

var read = function (filename){
    var promise = new Promise(function(resolve, reject){
        fs.readFile(filename, 'utf8', function(err, data){
            if (err){
                reject(err);
            }
            resolve(data);
        })
    });
    return promise;
}

var promises = [1, 2].map(function(fileno){
    return read('./text' + fileno + '.txt');
});

Promise.all(promises)
.then(function(contents){
    console.log(contents);
})
.catch(function(err){
    console.log("error caught: " + err);
})

上述代碼會并發(fā)執(zhí)行讀取text1.txttext2.txt的操作,當(dāng)兩個(gè)文件都讀取完畢時(shí),輸出它們的內(nèi)容,contents是一個(gè)數(shù)組,每個(gè)元素對應(yīng)promise數(shù)組的執(zhí)行結(jié)果 (順序完全一致),在這里就是text1.txttext2.txt的內(nèi)容。

Promise.race()

Promise.race方法同樣是將多個(gè) Promise實(shí)例,包裝成一個(gè)新的 Promise 實(shí)例。

const p = Promise.race([p1, p2, p3]);

只要p1、p2、p3之中有一個(gè)實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個(gè)率先改變的 Promise實(shí)例的返回值,就傳遞給p的回調(diào)函數(shù)。如果指定時(shí)間內(nèi)沒有獲得結(jié)果,就將 Promise 的狀態(tài)變?yōu)?code>reject,否則變?yōu)?code>resolve。

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

p
.then(console.log)
.catch(console.error);

上面代碼中,如果 5 秒之內(nèi)fetch方法無法返回結(jié)果,變量p的狀態(tài)就會變?yōu)?code>rejected,從而觸發(fā)catch方法指定的回調(diào)函數(shù)。

Promise.resolve()

有時(shí)需要將現(xiàn)有對象轉(zhuǎn)為 Promise 對象,Promise.resolve方法就起到這個(gè)作用。

Promise.resolve('foo')
// 等價(jià)于
new Promise(resolve => resolve('foo'))

Promise.resolve方法的參數(shù)分成四種情況。
(1)參數(shù)是一個(gè)Promise實(shí)例
如果參數(shù)是 Promise 實(shí)例,那么Promise.resolve將不做任何修改、原封不動地返回這個(gè)實(shí)例。
(2)參數(shù)是一個(gè)thenable對象
thenable對象指的是具有then方法的對象,比如說下面的對象:

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

Promise.resolve方法會將這個(gè)對象轉(zhuǎn)為 Promise 對象,然后就立即執(zhí)行thenable對象的then方法。
(3)參數(shù)不是具有then方法的對象,或根本就不是對象
如果參數(shù)是一個(gè)原始值,或是一個(gè)不具有then方法的對象,則Promise.reslove方法返回一個(gè)新的Promise對象,狀態(tài)為resolved
(4)不帶有任何參數(shù)
Promise.resolve方法允許調(diào)用時(shí)不帶參數(shù),直接返回一個(gè)resolved狀態(tài)的Promise 對象。

const p = Promise.resolve();

p.then(function () {
  // ...
});

Promise.reject()

Promise.reject(reason)方法也會返回一個(gè)新的Promise實(shí)例,該實(shí)例的狀態(tài)為rejected

const p = Promise.reject('出錯(cuò)了');
// 等同于
const p = new Promise((resolve, reject) => reject('出錯(cuò)了'))

p.then(null, function (s) {
  console.log(s)
});
// 出錯(cuò)了

上面代碼生成一個(gè) Promise對象的實(shí)例p,狀態(tài)為rejected,回調(diào)函數(shù)會立即執(zhí)行。
Promise.reject()方法的參數(shù),會原封不動地作為reject的理由,變成后續(xù)方法的參數(shù)。這一點(diǎn)與Promise.resolve方法不一致。

const thenable = {
  then(resolve, reject) {
    reject('出錯(cuò)了');
  }
};

Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true

Promise.try()

在實(shí)際開發(fā)的過程中,不知道或是不想?yún)^(qū)分,函數(shù)f是同步函數(shù)還是異步操作,但是想用Promise來處理它,因?yàn)檫@樣就可以不管f是否包含異步操作,都用then方法指定下一步的流程,用catch方法處理f拋出的錯(cuò)誤,一般就會采取下面的寫法。

Promise.resolve().then(f)

上面的寫法有一個(gè)缺點(diǎn),就是如果f是同步函數(shù),那么它會在本輪事件循環(huán)的末尾執(zhí)行。

const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now

Generator函數(shù)

Generator函數(shù)特征
(1)function關(guān)鍵字與函數(shù)名之間有一個(gè)星號
(2)函數(shù)體內(nèi)部使用yield表達(dá)式,定義不同的內(nèi)部狀態(tài)(yield在英語里的意思就是“產(chǎn)出”)。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

它內(nèi)部有兩個(gè)yield表達(dá)式(hello和world),即該函數(shù)有三個(gè)狀態(tài):hello,worldreturn語句。必須調(diào)用便利對象的next方法,讓指針移向下一個(gè)狀態(tài),也就是說每一次調(diào)用next方法,內(nèi)部指針就從函數(shù)頭部或是上一次停下來的地方開始執(zhí)行,知道遇到下一個(gè)yield表達(dá)式(或是return語句)為止,Generator函數(shù)是分段執(zhí)行的,yield表達(dá)式是暫停執(zhí)行的標(biāo)記,而next方法可以恢復(fù)執(zhí)行。
Generator函數(shù)的暫停執(zhí)行的效果,意味著可以把異步操作寫在yield語句里面,等到調(diào)用next方法時(shí)再往后執(zhí)行。這實(shí)際上等同于不需要寫回調(diào)函數(shù)了。所以,Generator函數(shù)的一個(gè)重要實(shí)際意義就是用來處理異步操作,改寫回調(diào)函數(shù)。

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

其中returnyield的區(qū)別:
(1)return終結(jié)遍歷,之后的yield語句都失效;next返回本次yield語句的返回值。
(2)return沒有參數(shù)的時(shí)候,返回{ value: undefined, done: true };next沒有參數(shù)的時(shí)候返回本次yield語句的返回值。
(3)return有參數(shù)的時(shí)候,覆蓋本次yield語句的返回值,也就是說,返回{ value: 參數(shù), done: true };next有參數(shù)的時(shí)候,覆蓋上次yield語句的返回值,返回值可能跟參數(shù)有關(guān)(參數(shù)參與計(jì)算的話),也可能跟參數(shù)無關(guān)(參數(shù)不參與計(jì)算)。

function *foo(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z);
}

var it = foo( 5 );

// 注意這里在調(diào)用next()方法時(shí)沒有傳入任何值
console.log( it.next() );       // { value:6, done:false }
console.log( it.next( 12 ) );   // { value:8, done:false }
console.log( it.next( 13 ) );   // { value:42, done:true }

你可以看到我們在構(gòu)造generator函數(shù)遍歷器的時(shí)候仍然可以傳遞參數(shù),這和普通的函數(shù)調(diào)用一樣,通過語句foo(5),我們將參數(shù)x的值設(shè)置為5。

第一次調(diào)用next()方法時(shí),沒有傳入任何值。為什么呢?因?yàn)榇藭r(shí)沒有yield表達(dá)式來接收我們傳入的值。

如果在第一次調(diào)用next()方法時(shí)傳入一個(gè)值,也不會有任何影響,該值會被拋棄掉。按照ES6標(biāo)準(zhǔn)的規(guī)定,此時(shí)generator函數(shù)會直接忽略掉該值(注意:在撰寫本文時(shí),ChromeFireFox瀏覽器都能很好地符合該規(guī)定,但其它瀏覽器可能并不完全符合,而且可能會拋出異常)。

表達(dá)式yield(x + 1)的返回值是6,然后第二個(gè)next(12)將12作為參數(shù)傳入,用來代替表達(dá)式yield(x + 1),因此變量y的值就是12 × 2,即24。隨后的yield(y / 3)(即yield(24 / 3))返回值8。然后第三個(gè)next(13)13作為參數(shù)傳入,用來代替表達(dá)式yield(y / 3),所以變量z的值是13。

最后,語句return (x + y + z)return (5 + 24 + 13),所以最終的返回值是42。

async/await

async
async/await雖然是ES7的關(guān)鍵字,但是通過編譯器的解譯,也是可以正常使用。async/await的語法很簡潔,直接作為一個(gè)關(guān)鍵字放到函數(shù)前面,用于表示該函數(shù)是一個(gè)異步函數(shù)。

async function timeout() {
    return 'hello world'
}
timeout();
//console.log(timeout())
console.log('雖然在后面,但是我先執(zhí)行');

分析:可以看出其實(shí)async 函數(shù)返回的是一個(gè)promise對象。所以,async內(nèi)部執(zhí)行的邏輯機(jī)理與promise是一致的。
await
await的語法是將其放在async函數(shù)中,表示暫停的意思??梢詫惒酱a像同步代碼一般書寫了。

// 2s 之后返回雙倍的值
function doubleAfter2seconds(num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2 * num)
        }, 2000);
    } )
}
async function testResult() {
    let first = await doubleAfter2seconds(30);
    let second = await doubleAfter2seconds(50);
    let third = await doubleAfter2seconds(30);
    console.log(first + second + third);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 異步編程對JavaScript語言太重要。Javascript語言的執(zhí)行環(huán)境是“單線程”的,如果沒有異步編程,根本...
    呼呼哥閱讀 7,405評論 5 22
  • 弄懂js異步 講異步之前,我們必須掌握一個(gè)基礎(chǔ)知識-event-loop。 我們知道JavaScript的一大特點(diǎn)...
    DCbryant閱讀 2,884評論 0 5
  • 十一、異步編程 原文:Asynchronous Programming譯者:飛龍協(xié)議:CC BY-NC-SA 4....
    布客飛龍閱讀 439評論 0 3
  • 那一天,他可能永遠(yuǎn)不會忘掉。 高一,孩童懵懂無知,十幾歲的孩子還對未來充滿向往。就在那時(shí)他遇到了她。他是普通班...
    池中鳥閱讀 352評論 0 0
  • 你永遠(yuǎn)不知道 這個(gè)世界上 誰 會以怎樣的方式向你無聲告別 不 也許你知道 畢竟是向你告別
    知青的葉閱讀 172評論 0 0

友情鏈接更多精彩內(nèi)容