版權聲明:本文為部落客原創文章,遵循 CC 4.0 by-sa 版權協定,轉載請附上原文出處連結和本聲明。
本文連結:https://ligang.blog.csdn.net/article/details/70843713
ES6誕生以前,異步程式設計的方法,大概有如下四種:回調函數、事件監聽、釋出/訂閱、Promise對象;ES6中,引入了Generator函數;ES7中,async更是将異步程式設計帶入了一個全新的階段。
十四、Promise對象
Promise,就是一個對象,用來傳遞異步操作的消息,避免了層層嵌套的回調函數。它代表了某個未來才會知道結果的事件(通常是一個異步操作),并且這個事件提供統一的API,可供進一步處理。
(1)對象的狀态不受外界影響。有三種狀态:Pending(進行中)、Resolved(已完成,又稱Fulfilled)和Rejected(已失敗)。
(2)一旦狀态改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀态改變,隻有兩種可能:從Pending變為Resolved和從Pending變為Rejected。隻要這兩種情況發生,狀态就凝固了,不會再變了,會一直保持這個結果。
缺點:
- 無法取消Promise,一旦建立它就會執行,無法中途取消
- 如果不設定回調函數,Promise内部抛出的錯誤,不會反應到外部
- 當處于Pending狀态時,無法得知目前進展到哪一個階段(剛剛開始還是即将完成)。
更多Promise請參考:http://blog.csdn.net/ligang2585116/article/details/51417334
十五、Generator函數
從計算機角度看,生成器是一種類協程或半協程,它提供了一種可以通過特定語句或方法使其執行對象暫停的功能。
Generator函數,傳回一個部署了Iterator接口的周遊器對象,用來操作内部指針。以後,每次調用周遊器對象的next方法,就會傳回一個有着value和done兩個屬性的對象。value屬性表示目前的内部狀态的值,是yield語句後面那個表達式的值;done屬性是一個布爾值,表示是否周遊結束。
yield [[expression]]
yield 關鍵字使生成器函數暫停執行,并傳回跟在它後面的表達式的目前值。可以把它想成是return關鍵字的一個基于生成器的版本,但其并非退出函數體,而是切出目前函數的運作時,與此同時可以将一個值帶到主線程中。yield語句是暫停執行的标記,而next方法可以恢複執行。
function* gen(){
yield 'li';
yield 'gang'; // 有誤!!!
return '!';
}
var g = gen();
g.next(); // {value: 'li', done: false}
g.next(); // {value: 'gang', done: false}
g.next(); // {value: '!', done: true}
複制
(1)遇到yield語句,就暫停執行後面的操作,并将緊跟在yield後面的那個表達式的值,作為傳回的對象的value屬性值;
(2)下一次調用next方法時,再繼續往下執行,直到遇到下一個yield語句;
(3)如果沒有再遇到新的yield語句,就一直運作到函數結束,直到return語句為止,并将return語句後面的表達式的值,作為傳回的對象的value屬性值;
(4)如果該函數沒有return語句,則傳回的對象的value屬性值為undefined。
需要注意的是,yield語句後面的表達式,隻有當調用next方法、内部指針指向該語句時才會執行,是以等于為JavaScript提供了手動的“惰性求值”(Lazy Evaluation)的文法功能。
function* gen() {
yield 123 + 456;
}
複制
上述示例中,yield後面的表達式123 + 456,不會立即求值,隻會在next方法将指針移到這一句時,才會求值。Generator函數也可以不用yield語句,這時就變成了一個單純的暫緩執行函數。
function* f() {
console.log('執行了!')
}
let gen = f();
setTimeout(function () {
gen.next()
}, 2000);
複制
next方法的參數
注意: yield句本身沒有傳回值(傳回undefined)。next方法可以帶一個參數,該參數就會被當作上一個yield語句的傳回值。
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next(); // Object{value:6, done:false}
a.next(); // Object{value:NaN, done:false}
a.next(); // Object{value:NaN, done:true}
var b = foo(5);
b.next(); // { value:6, done:false }
b.next(12); // { value:8, done:false }
b.next(13); // { value:42, done:true }
複制
next方法不帶參數,導緻y的值等于
2 * undefined
(即NaN),除以3以後還是NaN;next方法提供參數,第一次調用b的next方法時,傳回x+1的值6;第二次調用next方法,将上一次yield語句的值設為12,是以y等于24,傳回y/3的值8。
注意:這個功能有很重要的文法意義。Generator函數從暫停狀态到恢複運作,它的上下文狀态(context)是不變的。通過next方法的參數,就有辦法在Generator函數開始運作之後,繼續向函數體内部注入值。也就是說,可以在Generator函數運作的不同階段,從外部向内部注入不同的值,進而調整函數行為。
function* f() {
for(let i=0; true; i++) {
let reset = yield i;
if(reset) { i = -1; }
}
}
let g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
複制
for…of循環
for...of
循環可以自動周遊Generator函數時生成的Iterator對象,且此時不再需要調用next方法。
function *foo() {
yield 1;
yield 2;
return 3;
}
for (let v of foo()) {
console.log(v);
}
複制
利用Generator函數和
for...of
循環,實作斐波那契數列
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
複制
yield* [[expression]]
yield*
一個可疊代對象,就相當于把這個可疊代對象的所有疊代值分次 yield 出去。表達式本身的值就是目前可疊代對象疊代完畢(當done為true時)時的傳回值。
function* gen(){
yield [1, 2];
yield* [3, 4];
}
var g = gen();
g.next(); // {value: Array[2], done: false}
g.next(); // {value: 3, done: false}
g.next(); // {value: 4, done: false}
g.next(); // {value: undefined, done: true}
複制
判斷是否為Generator函數
function isGenerator(fn){
// 生成器示例必帶@@toStringTag屬性
if(Symbol && Symbol.toStringTag) {
return fn[Symbol.toStringTag] === 'GeneratorFunction';
}
}
複制
十六、async函數
async函數可以了解為Generator函數的文法糖,使用async内置了執行器,無需調用next方法進行逐漸調用。且其傳回值為Promise。
基本用法
async
函數傳回一個 Promise 對象,可以使用
then
方法添加回調函數。當函數執行的時候,一旦遇到
await
就會先傳回,等到異步操作完成,再接着執行函數體内後面的語句。
async function gen(x){
var y = await x + 2;
var z = await y + 2;
return z;
}
gen(1).then(result => console.log(result), error => console.log(error));
gen(1).then(result => console.log(result)).catch(error => console.log(error));
複制
5秒以後,輸出
hello world
/*回調方式*/
function test(callback) {
setTimeout(() => callback('hello world'), 5000);
}
var res = test((value) => console.log(value));
console.log(res);
/*Promise方式*/
function test() {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve('hello world');
}, 5000)
});
}
test().then((value) => console.log(value));
/*async方式*/
async function test() {
return await new Promise((resolve, reject) => {
setTimeout(() => {
return resolve('hello world');
}, 5000)
});
}
test().then((value) => console.log(value));
複制
await指令
正常情況下,await指令後是一個Promise對象。如果不是,會被轉成一個立即resolve的Promise對象。
/*成功情況*/
async function f() {
return await 123;
}
f().then(value => console.log(value)); // 123
/*失敗情況*/
async function f() {
return Promise.reject('error');
}
f().catch(e => console.error(e)); // error
複制
錯誤處理
async function f() {
await new Promise((resolve, reject) => {
throw new Error('出錯了');
})
}
f().then(v => console.log(v))
.catch(e => console.error(e)); // Error: 出錯了
複制
注意點
await隻能用在async函數中,不能用在普通函數中
/* 錯誤處理 */
function f(db) {
let docs = [1, 2, 3];
for(let doc of docs) {
await db.push(doc);
}
return db; // Uncaught SyntaxError: Unexpected identifier
}
/* 正确處理(順序執行) */
async function f(db) {
let docs = [1, 2, 3];
for(let doc of docs) {
await db.push(doc);
}
return db;
}
let ary = [];
f(ary);
console.log(ary); // [1, 2, 3]
/* 正确處理(并發執行) */
async function f(db) {
let docs = [1, 2, 3];
let promises = docs.map(doc => db.push(doc));
db = await Promise.all(promises);
return db;
}
let ary = [];
f(ary);
console.log(ary); // [1, 2, 3]
複制
await後面可能存在reject,需要進行try…catch代碼塊中
async function f() {
try {
await Promise.reject('出錯了');
} catch(e) {
console.error(e);
}
return Promise.resolve('hello');
}
f().then(v => console.log(v)); // 出錯了 hello
複制
多個異步操作,如果沒有繼承關系,最好同時觸發
async function f() {
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
return [foo, bar];
}
複制
十七、Promise和async使用場景
下述描述的使用場景,隻是自己在開發中經常遇到的,并不一定完全符合所有人的使用,隻是這樣用起來會很便利和實作一些比較難處理的情況。
一個過程中同時存在異步、同步情況,請使用Promise
/*正常方式,錯誤!不能實作*/
function test(bool) {
// bool為true,直接傳回"hello"
// bool為false,進行異步請求,這裡使用setTimeout代替異步過程
if(bool) {
return "hello";
} else {
setTimeout(() => {
return "world";
}, 5000);
}
}
test(true); // "hello"
test(false); // 無任何輸出内容
/*Promise正确方式*/
function test(bool) {
// bool為true,直接傳回"hello"
// bool為false,進行異步請求,這裡使用setTimeout代替異步過程
return new Promise((resolve, reject) => {
if(bool) {
return resolve("hello");
} else {
setTimeout(() => {
return resolve("world");
}, 5000);
}
});
}
test(true).then(v => console.log(v)); // 'hello'
test(false).then(v => console.log(v)); // 大約5s後輸出 'world'
複制
包裹本身不支援async的函數,且hold住目前請求
import fs from "fs";
async function readFile(filepath) {
return await new Promise((resolve, reject) => {
fs.stat(filepath, (error) => {
if(error) {
return reject("檔案不存在!");
}
let content = fs.readFileSync(filepath, "utf8");
return resolve(content);
})
})
}
// 測試
readFile(__filename).then((content) => {
console.log(content)
}).catch((error) => {
console.error(error);
});
複制