鍍金池/ 教程/ HTML/ Promise 對(duì)象
數(shù)組的擴(kuò)展
Class和Module
Set 和 Map 數(shù)據(jù)結(jié)構(gòu)
異步操作
對(duì)象的擴(kuò)展
Generator 函數(shù)
數(shù)值的擴(kuò)展
變量的解構(gòu)賦值
Iterator 和 for...of 循環(huán)
Promise 對(duì)象
參考鏈接
ECMAScript 6簡介
作者簡介
字符串的擴(kuò)展
編程風(fēng)格
let 和 const 命令
函數(shù)的擴(kuò)展

Promise 對(duì)象

基本用法

ES6 原生提供了 Promise 對(duì)象。所謂 Promise 對(duì)象,就是代表了某個(gè)未來才會(huì)知道結(jié)果的事件(通常是一個(gè)異步操作),并且這個(gè)事件提供統(tǒng)一的 API,可供進(jìn)一步處理。

有了 Promise 對(duì)象,就可以將異步操作以同步操作的流程表達(dá)出來,避免了層層嵌套的回調(diào)函數(shù)。此外,Promise 對(duì)象提供的接口,使得控制異步操作更加容易。Promise 對(duì)象的概念的詳細(xì)解釋,請(qǐng)參考《JavaScript標(biāo)準(zhǔn)參考教程》。

ES6 的 Promise 對(duì)象是一個(gè)構(gòu)造函數(shù),用來生成 Promise 實(shí)例。


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

promise.then(function(value) {
  // success
}, function(value) {
  // failure
});

上面代碼中,Promise 構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù),該函數(shù)的兩個(gè)參數(shù)分別是 resolve 方法和 reject 方法。如果異步操作成功,則用 resolve 方法將 Promise 對(duì)象的狀態(tài),從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?resolved);如果異步操作失敗,則用 reject 方法將 Promise 對(duì)象的狀態(tài),從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected)。

Promise 實(shí)例生成以后,可以用 then 方法分別指定 resolve 方法和 reject 方法的回調(diào)函數(shù)。

下面是一個(gè)使用 Promise 對(duì)象的簡單例子。


function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

timeout(100).then(() => {
  console.log('done');
});

上面代碼中,timeout 方法返回一個(gè) Promise 實(shí)例,表示一段時(shí)間以后才會(huì)發(fā)生的結(jié)果。一旦 Promise 對(duì)象的狀態(tài)變?yōu)?resolved,就會(huì)觸發(fā) then 方法綁定的回調(diào)函數(shù)。

下面是一個(gè)用 Promise 對(duì)象實(shí)現(xiàn)的 Ajax 操作的例子。


var getJSON = function(url) {
  var promise = new Promise(function(resolve, reject){
    var client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

    function handler() {
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出錯(cuò)了', error);
});

上面代碼中,getJSON 是對(duì) XMLHttpRequest 對(duì)象的封裝,用于發(fā)出一個(gè)針對(duì) JSON 數(shù)據(jù)的 HTTP 請(qǐng)求,并且返回一個(gè) Promise 對(duì)象。需要注意的是,在 getJSON 內(nèi)部,resolve 方法和 reject 方法調(diào)用時(shí),都帶有參數(shù)。

如果調(diào)用 resolve 方法和 reject 方法時(shí)帶有參數(shù),那么它們的參數(shù)會(huì)被傳遞給回調(diào)函數(shù)。reject 方法的參數(shù)通常是 Error 對(duì)象的實(shí)例,表示拋出的錯(cuò)誤;resolve 方法的參數(shù)除了正常的值以外,還可能是另一個(gè) Promise 實(shí)例,表示異步操作的結(jié)果有可能是一個(gè)值,也有可能是另一個(gè)異步操作,比如像下面這樣。

var p1 = new Promise(function(resolve, reject){
  // ...
});

var p2 = new Promise(function(resolve, reject){
  // ...
  resolve(p1);
})

上面代碼中,p1 和 p2 都是 Promise 的實(shí)例,但是 p2 的 resolve 方法將 p1 作為參數(shù),p1 的狀態(tài)就會(huì)傳遞給 p2。

注意,這時(shí) p1 的狀態(tài)決定了 p2 的狀態(tài)。如果 p1 的狀態(tài)是 pending,那么 p2 的回調(diào)函數(shù)就會(huì)等待 p1 的狀態(tài)改變;如果 p1 的狀態(tài)已經(jīng)是 fulfilled 或者 rejected,那么 p2 的回調(diào)函數(shù)將會(huì)立刻執(zhí)行。

Promise.prototype.then()

Promise.prototype.then 方法返回的是一個(gè)新的Promise對(duì)象,因此可以采用鏈?zhǔn)綄懛ǎ磘hen方法后面再調(diào)用另一個(gè)then方法。


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

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

如果前一個(gè)回調(diào)函數(shù)返回的是Promise對(duì)象,這時(shí)后一個(gè)回調(diào)函數(shù)就會(huì)等待該P(yáng)romise對(duì)象有了運(yùn)行結(jié)果,才會(huì)進(jìn)一步調(diào)用。


getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // ...
});

then方法還可以接受第二個(gè)參數(shù),表示Promise對(duì)象的狀態(tài)變?yōu)閞ejected時(shí)的回調(diào)函數(shù)。

Promise.prototype.catch()

Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)。


getJSON("/posts.json").then(function(posts) {
  // ...
}).catch(function(error) {
  // 處理前一個(gè)回調(diào)函數(shù)運(yùn)行時(shí)發(fā)生的錯(cuò)誤
  console.log('發(fā)生錯(cuò)誤!', error);
});

上面代碼中,getJSON方法返回一個(gè)Promise對(duì)象,如果該對(duì)象運(yùn)行正常,則會(huì)調(diào)用then方法指定的回調(diào)函數(shù);如果該方法拋出錯(cuò)誤,則會(huì)調(diào)用catch方法指定的回調(diào)函數(shù),處理這個(gè)錯(cuò)誤。

下面是一個(gè)例子。


var promise = new Promise(function(resolve, reject) {
  throw new Error('test')
});
promise.catch(function(error) { console.log(error) });
// Error: test

上面代碼中,Promise拋出一個(gè)錯(cuò)誤,就被catch方法指定的回調(diào)函數(shù)捕獲。

如果Promise狀態(tài)已經(jīng)變成resolved,再拋出錯(cuò)誤是無效的。


var 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在resolve語句后面,再拋出錯(cuò)誤,不會(huì)被捕獲,等于沒有拋出。

Promise對(duì)象的錯(cuò)誤具有“冒泡”性質(zhì),會(huì)一直向后傳遞,直到被捕獲為止。也就是說,錯(cuò)誤總是會(huì)被下一個(gè)catch語句捕獲。


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

上面代碼中,一共有三個(gè)Promise對(duì)象:一個(gè)由getJSON產(chǎn)生,兩個(gè)由then產(chǎn)生。它們之中任何一個(gè)拋出的錯(cuò)誤,都會(huì)被最后一個(gè)catch捕獲。

跟傳統(tǒng)的try/catch代碼塊不同的是,如果沒有使用catch方法指定錯(cuò)誤處理的回調(diào)函數(shù),Promise對(duì)象拋出的錯(cuò)誤不會(huì)傳遞到外層代碼,即不會(huì)有任何反應(yīng)。


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

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

上面代碼中,someAsyncThing函數(shù)產(chǎn)生的Promise對(duì)象會(huì)報(bào)錯(cuò),但是由于沒有調(diào)用catch方法,這個(gè)錯(cuò)誤不會(huì)被捕獲,也不會(huì)傳遞到外層代碼,導(dǎo)致運(yùn)行后沒有任何輸出。


var promise = new Promise(function(resolve, reject) {
  resolve("ok");
  setTimeout(function() { throw new Error('test') }, 0)
});
promise.then(function(value) { console.log(value) });
// ok
// Uncaught Error: test

上面代碼中,Promise指定在下一輪“事件循環(huán)”再拋出錯(cuò)誤,結(jié)果由于沒有指定catch語句,就冒泡到最外層,成了未捕獲的錯(cuò)誤。

Node.js有一個(gè)unhandledRejection事件,專門監(jiān)聽未捕獲的reject錯(cuò)誤。

process.on('unhandledRejection', function (err, p) {
  console.error(err.stack)
});

上面代碼中,unhandledRejection事件的監(jiān)聽函數(shù)有兩個(gè)參數(shù),第一個(gè)是錯(cuò)誤對(duì)象,第二個(gè)是報(bào)錯(cuò)的Promise實(shí)例,它可以用來了解發(fā)生錯(cuò)誤的環(huán)境信息。。

需要注意的是,catch方法返回的還是一個(gè)Promise對(duì)象,因此后面還可以接著調(diào)用then方法。


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

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
}).then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on

上面代碼運(yùn)行完catch方法指定的回調(diào)函數(shù),會(huì)接著運(yùn)行后面那個(gè)then方法指定的回調(diào)函數(shù)。

catch方法之中,還能再拋出錯(cuò)誤。


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

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行會(huì)報(bào)錯(cuò),因?yàn)閥沒有聲明
  y + 2;
}).then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]

上面代碼中,catch方法拋出一個(gè)錯(cuò)誤,因?yàn)楹竺鏇]有別的catch方法了,導(dǎo)致這個(gè)錯(cuò)誤不會(huì)被捕獲,也不會(huì)到傳遞到外層。如果改寫一下,結(jié)果就不一樣了。


someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行會(huì)報(bào)錯(cuò),因?yàn)閥沒有聲明
  y + 2;
}).catch(function(error) {
  console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]

上面代碼中,第二個(gè)catch方法用來捕獲,前一個(gè)catch方法拋出的錯(cuò)誤。

Promise.all(),Promise.race()

Promise.all方法用于將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例。


var p = Promise.all([p1,p2,p3]);

上面代碼中,Promise.all方法接受一個(gè)數(shù)組作為參數(shù),p1、p2、p3都是Promise對(duì)象的實(shí)例。(Promise.all方法的參數(shù)不一定是數(shù)組,但是必須具有iterator接口,且返回的每個(gè)成員都是Promise實(shí)例。)

p的狀態(tài)由p1、p2、p3決定,分成兩種情況。

(1)只有p1、p2、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會(huì)變成fulfilled,此時(shí)p1、p2、p3的返回值組成一個(gè)數(shù)組,傳遞給p的回調(diào)函數(shù)。

(2)只要p1、p2、p3之中有一個(gè)被rejected,p的狀態(tài)就變成rejected,此時(shí)第一個(gè)被reject的實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)。

下面是一個(gè)具體的例子。


// 生成一個(gè)Promise對(duì)象的數(shù)組
var promises = [2, 3, 5, 7, 11, 13].map(function(id){
  return getJSON("/post/" + id + ".json");
});

Promise.all(promises).then(function(posts) {
  // ...  
}).catch(function(reason){
  // ...
});

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


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

上面代碼中,只要p1、p2、p3之中有一個(gè)實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個(gè)率先改變的Promise實(shí)例的返回值,就傳遞給p的返回值。

如果Promise.all方法和Promise.race方法的參數(shù),不是Promise實(shí)例,就會(huì)先調(diào)用下面講到的Promise.resolve方法,將參數(shù)轉(zhuǎn)為Promise實(shí)例,再進(jìn)一步處理。

Promise.resolve(),Promise.reject()

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


var jsPromise = Promise.resolve($.ajax('/whatever.json'));

上面代碼將jQuery生成deferred對(duì)象,轉(zhuǎn)為一個(gè)新的ES6的Promise對(duì)象。

如果Promise.resolve方法的參數(shù),不是具有then方法的對(duì)象(又稱thenable對(duì)象),則返回一個(gè)新的Promise對(duì)象,且它的狀態(tài)為fulfilled。


var p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});
// Hello

上面代碼生成一個(gè)新的Promise對(duì)象的實(shí)例p,它的狀態(tài)為fulfilled,所以回調(diào)函數(shù)會(huì)立即執(zhí)行,Promise.resolve方法的參數(shù)就是回調(diào)函數(shù)的參數(shù)。

所以,如果希望得到一個(gè)Promise對(duì)象,比較方便的方法就是直接調(diào)用Promise.resolve方法。


var p = Promise.resolve();

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

上面代碼的變量p就是一個(gè)Promise對(duì)象。

如果Promise.resolve方法的參數(shù)是一個(gè)Promise對(duì)象的實(shí)例,則會(huì)被原封不動(dòng)地返回。

Promise.reject(reason)方法也會(huì)返回一個(gè)新的Promise實(shí)例,該實(shí)例的狀態(tài)為rejected。Promise.reject方法的參數(shù)reason,會(huì)被傳遞給實(shí)例的回調(diào)函數(shù)。


var p = Promise.reject('出錯(cuò)了');

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

上面代碼生成一個(gè)Promise對(duì)象的實(shí)例p,狀態(tài)為rejected,回調(diào)函數(shù)會(huì)立即執(zhí)行。

Generator函數(shù)與Promise的結(jié)合

使用Generator函數(shù)管理流程,遇到異步操作的時(shí)候,通常返回一個(gè)Promise對(duì)象。


function getFoo () {
  return new Promise(function (resolve, reject){
    resolve('foo');
  });
}

var g = function* () {
  try {
    var foo = yield getFoo();
    console.log(foo);
  } catch (e) {
    console.log(e);
  }
};

function run (generator) {
  var it = generator();

  function go(result) {
    if (result.done) return result.value;

    return result.value.then(function (value) {
      return go(it.next(value));
    }, function (error) {
      return go(it.throw(value));
    });
  }

  go(it.next());
}

run(g);

上面代碼的Generator函數(shù)g之中,有一個(gè)異步操作getFoo,它返回的就是一個(gè)Promise對(duì)象。函數(shù)run用來處理這個(gè)Promise對(duì)象,并調(diào)用下一個(gè)next方法。

async函數(shù)

概述

async函數(shù)與Promise、Generator函數(shù)一樣,是用來取代回調(diào)函數(shù)、解決異步操作的一種方法。它本質(zhì)上是Generator函數(shù)的語法糖。async函數(shù)并不屬于ES6,而是被列入了ES7,但是traceur、Babel.js、regenerator等轉(zhuǎn)碼器已經(jīng)支持這個(gè)功能,轉(zhuǎn)碼后立刻就能使用。

下面是一個(gè)Generator函數(shù),依次讀取兩個(gè)文件。

var fs = require('fs');

var readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) reject(error);
      resolve(data);
    });
  });
};

var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

上面代碼中,readFile函數(shù)是fs.readFile的Promise版本。

寫成async函數(shù),就是下面這樣。

var asyncReadFile = async function (){
  var f1 = await readFile('/etc/fstab');
  var f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

一比較就會(huì)發(fā)現(xiàn),async函數(shù)就是將Generator函數(shù)的星號(hào)(*)替換成async,將yield替換成await,僅此而已。

async函數(shù)對(duì)Generator函數(shù)的改進(jìn),體現(xiàn)在以下三點(diǎn)。

(1)內(nèi)置執(zhí)行器。Generator函數(shù)的執(zhí)行必須靠執(zhí)行器,而async函數(shù)自帶執(zhí)行器。也就是說,async函數(shù)的執(zhí)行,與普通函數(shù)一模一樣,只要一行。

var result = asyncReadFile();

(2)更好的語義。async和await,比起星號(hào)和yield,語義更清楚了。async表示函數(shù)里有異步操作,await表示緊跟在后面的表達(dá)式需要等待結(jié)果。

(3)更廣的適用性。co函數(shù)庫約定,yield命令后面只能是Thunk函數(shù)或Promise對(duì)象,而async函數(shù)的await命令后面,可以跟Promise對(duì)象和原始類型的值(數(shù)值、字符串和布爾值,但這時(shí)等同于同步操作)。

實(shí)現(xiàn)

async函數(shù)的實(shí)現(xiàn),就是將Generator函數(shù)和自動(dòng)執(zhí)行器,包裝在一個(gè)函數(shù)里。

async function fn(args){
  // ...
}

// 等同于

function fn(args){
  return spawn(function*() {
    // ...
  });
}

所有的async函數(shù)都可以寫成上面的第二種形式,其中的spawn函數(shù)就是自動(dòng)執(zhí)行器。

下面給出spawn函數(shù)的實(shí)現(xiàn),基本就是前文自動(dòng)執(zhí)行器的翻版。

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    var gen = genF();
    function step(nextF) {
      try {
        var next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

用法

同Generator函數(shù)一樣,async函數(shù)返回一個(gè)Promise對(duì)象,可以使用then方法添加回調(diào)函數(shù)。當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到await就會(huì)先返回,等到觸發(fā)的異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語句。

下面是一個(gè)例子。


async function getStockPriceByName(name) {
  var symbol = await getStockSymbol(name);
  var stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result){
  console.log(result);
});

上面代碼是一個(gè)獲取股票報(bào)價(jià)的函數(shù),函數(shù)前面的async關(guān)鍵字,表明該函數(shù)內(nèi)部有異步操作。調(diào)用該函數(shù)時(shí),會(huì)立即返回一個(gè)Promise對(duì)象。

上面的例子用Generator函數(shù)表達(dá),就是下面這樣。

function getStockPriceByName(name) {
  return spawn(function*(name) {
    var symbol = yield getStockSymbol(name);
    var stockPrice = yield getStockPrice(symbol);
    return stockPrice;
  });
}

上面的例子中,spawn函數(shù)是一個(gè)自動(dòng)執(zhí)行器,由JavaScript引擎內(nèi)置。它的參數(shù)是一個(gè)Generator函數(shù)。async...await結(jié)構(gòu)本質(zhì)上,是在語言層面提供的異步任務(wù)的自動(dòng)執(zhí)行器。

下面是一個(gè)更一般性的例子,指定多少毫秒后輸出一個(gè)值。


function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value)
}

asyncPrint('hello world', 50);

上面代碼指定50毫秒以后,輸出“hello world”。

注意點(diǎn)

await命令后面的Promise對(duì)象,運(yùn)行結(jié)果可能是rejected,所以最好把a(bǔ)wait命令放在try...catch代碼塊中。


async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一種寫法

async function myFunction() {
  await somethingThatReturnsAPromise().catch(function (err){
    console.log(err);
  };
}

await命令只能用在async函數(shù)之中,如果用在普通函數(shù),就會(huì)報(bào)錯(cuò)。


async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 報(bào)錯(cuò)
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

上面代碼會(huì)報(bào)錯(cuò),因?yàn)閍wait用在普通函數(shù)之中了。但是,如果將forEach方法的參數(shù)改成async函數(shù),也有問題。


async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 可能得到錯(cuò)誤結(jié)果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}

上面代碼可能不會(huì)正常工作,原因是這時(shí)三個(gè)db.post操作將是并發(fā)執(zhí)行,也就是同時(shí)執(zhí)行,而不是繼發(fā)執(zhí)行。正確的寫法是采用for循環(huán)。


async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}

如果確實(shí)希望多個(gè)請(qǐng)求并發(fā)執(zhí)行,可以使用Promise.all方法。


async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的寫法

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

ES6將await增加為保留字。使用這個(gè)詞作為標(biāo)識(shí)符,在ES5是合法的,在ES6將拋出SyntaxError。

與Promise、Generator的比較

我們通過一個(gè)例子,來看Async函數(shù)與Promise、Generator函數(shù)的區(qū)別。

假定某個(gè)DOM元素上面,部署了一系列的動(dòng)畫,前一個(gè)動(dòng)畫結(jié)束,才能開始后一個(gè)。如果當(dāng)中有一個(gè)動(dòng)畫出錯(cuò),就不再往下執(zhí)行,返回上一個(gè)成功執(zhí)行的動(dòng)畫的返回值。

首先是Promise的寫法。


function chainAnimationsPromise(elem, animations) {

  // 變量ret用來保存上一個(gè)動(dòng)畫的返回值
  var ret = null;

  // 新建一個(gè)空的Promise
  var p = Promise.resolve();

  // 使用then方法,添加所有動(dòng)畫
  for(var anim in animations) {
    p = p.then(function(val) {
      ret = val;
      return anim(elem);
    })
  }

  // 返回一個(gè)部署了錯(cuò)誤捕捉機(jī)制的Promise
  return p.catch(function(e) {
    /* 忽略錯(cuò)誤,繼續(xù)執(zhí)行 */
  }).then(function() {
    return ret;
  });

}

雖然Promise的寫法比回調(diào)函數(shù)的寫法大大改進(jìn),但是一眼看上去,代碼完全都是Promise的API(then、catch等等),操作本身的語義反而不容易看出來。

接著是Generator函數(shù)的寫法。


function chainAnimationsGenerator(elem, animations) {

  return spawn(function*() {
    var ret = null;
    try {
      for(var anim of animations) {
        ret = yield anim(elem);
      }
    } catch(e) {
      /* 忽略錯(cuò)誤,繼續(xù)執(zhí)行 */
    }
      return ret;
  });

}

上面代碼使用Generator函數(shù)遍歷了每個(gè)動(dòng)畫,語義比Promise寫法更清晰,用戶定義的操作全部都出現(xiàn)在spawn函數(shù)的內(nèi)部。這個(gè)寫法的問題在于,必須有一個(gè)任務(wù)運(yùn)行器,自動(dòng)執(zhí)行Generator函數(shù),上面代碼的spawn函數(shù)就是自動(dòng)執(zhí)行器,它返回一個(gè)Promise對(duì)象,而且必須保證yield語句后面的表達(dá)式,必須返回一個(gè)Promise。

最后是Async函數(shù)的寫法。


async function chainAnimationsAsync(elem, animations) {
  var ret = null;
  try {
    for(var anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {
    /* 忽略錯(cuò)誤,繼續(xù)執(zhí)行 */
  }
  return ret;
}

可以看到Async函數(shù)的實(shí)現(xiàn)最簡潔,最符合語義,幾乎沒有語義不相關(guān)的代碼。它將Generator寫法中的自動(dòng)執(zhí)行器,改在語言層面提供,不暴露給用戶,因此代碼量最少。如果使用Generator寫法,自動(dòng)執(zhí)行器需要用戶自己提供。

上一篇:參考鏈接下一篇:let 和 const 命令