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對象
ES6:Generator函數(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ù)分別是resolve和reject。他們是兩個(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.txt和text2.txt的操作,當(dāng)兩個(gè)文件都讀取完畢時(shí),輸出它們的內(nèi)容,contents是一個(gè)數(shù)組,每個(gè)元素對應(yīng)promise數(shù)組的執(zhí)行結(jié)果 (順序完全一致),在這里就是text1.txt和text2.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,world 和 return語句。必須調(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 }
其中return和yield的區(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í),Chrome和FireFox瀏覽器都能很好地符合該規(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);
}