[ES6] 異步操作 async 函數

Async 函数是什么?

一句话,它就是 Generator 函数的语法糖。
但是Generator 還是太複雜

async函数:

1
2
3
4
5
6
const asyncReadFile = async function () {
const afile = await readFile('/etc/fstab');
const bfile = await readFile('/etc/shells');
console.log(afile.toString());
console.log(bfile.toString());
};

Async函数对 Generator 函数的改进,以下四点。

(1)内置執行器。
以下代碼调用了asyncReadFile函数,然后它就会执行,输出最后结果。

1
asyncReadFile();

(2)更好的語意。
async表示函数里有異步操作。
async和await,比起星號和yield(也就是Generator函数),語意更清楚了。
await命令只能用在async函数之中,如果用在普通函数,就會報錯。

1
2
3
async function () {
//...裡面有異步操作
}

(3)更廣的適用性。
yield命令后面只能是 Thunk 函数或 Promise 对象
而async函数的await命令后面,可以是 Promise 對象和原始类型的值
(数值、字符串和布尔值,但这时等同于同步操作)

1
2
3
4
5
async function () {
//123就是原始類型的值
//也可以講解成await命令后面Promise 对象,狀態同步變成resolve
return await 123;
}

(4)返回值是 Promise。
async函数的返回值是 Promise 對象!
这比 Generator 函数的返回值是 Iterator 對象方便多了。
附上:何謂promise對象?

Async 函数有多种使用形式

螢幕快照 2017-11-16 下午12.13.53.png

其實 async 函数的语法规则总体上比较简单
需要了解的是错误處理機制

Promise 对象

前面提到,async函数的返回值是 Promise 對象!
而async 裡面可以寫很多個await,所以可以視為很多個Promise對象集合!
resolve(success)會被then接收到!
reject(error)會被catch接收到!

1
2
3
4
5
6
7
async function f() {
return 'hello world';
//這邊等同事返回一個”原始类型的值“
//又等於是一個狀態輸出resolve
}
f().then(v => console.log(v))
// 輸出"hello world"

Promise 的狀態一旦改變,就永久保持该狀態,不會再變

1
2
3
4
5
6
7
8
9
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
// 最後丟出的 Error 就沒被執行了!

Error & then & catch

async函数内部抛出Error
1.会導致返回的 Promise 对象变为reject状态。
2.抛出的错误对象会被then()catch方法回调函数接收到。
3.只要拋出錯誤,Promise狀態改變,會立即中斷所有後面的程式執行!

1
2
3
4
5
6
7
8
9
10
11
async function f() {
await Promise.reject('出错了');
// throw new Error('出错了');
return 123;
}
f().then(
v => console.log("then",v),
//e => console.log("then",e)
).catch(
e => console.log("catch",e)
)

同上,我寫的範例程式碼:https://jsbin.com/zikiqod/edit?js,console
(希望大家玩看看,並確切了解上面三點)
(並判斷throw new Error();reject();之下,catch所接收到的差別)

參照:ECMAScript 6 入门 裡面的一段話

Over the past 24 hours I’ve been reflecting on my life & I’ve realized only one thing. I need a medieval battle axe.

> 一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

建議不要使用,then(resolve,reject)的方式

1
2
3
4
f().then(
v => console.log("then",v),
//e => console.log("then",e)
)

請使用catch()的方式來取得error!

1
2
3
4
5
6
f().then(
v => console.log("then",v),
//e => console.log("then",e)
).catch(
e => console.log("catch",e)
)

複雜的機制處理

1
2
3
4
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}

情況是,當我們有兩個await異步操作,
結果第一支檔案的異步Promise狀態,
已經拋出錯誤給外面的Promise狀態。
這時候..第二支檔案就不會執行!
而錯誤只會抓到f()當前遇到的reject(error);資訊

所以我们希望即使前一个異步操作失敗,也不要中断后面的異步操作。
可以將第一个await放在try...catch結構里面
這樣不管這個異步操作是否成功,第二个await都会执行。

1
2
3
4
5
6
7
8
9
10
11
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

另一種簡寫:await後面的 Promise 對象再跟一个catch方法,來除錯

1
2
3
4
5
6
7
8
9
10
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

參照:ECMAScript 6 入门 裡面的一錯誤處理

使用注意點

  1. 把await命令放在try...catch代码块中。
1
2
3
4
5
6
7
8
9
10
11
async function chainAnimationsAsync(elem, animations) {
let ret = null;
try {
for(let anim of animations) {
ret = await anim(elem);
}
} catch(e) {
/* 忽略错误,继续执行 */
}
return ret;
}
  1. 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。這是等待觸發的方式
1
2
let foo = await getFoo();
let bar = await getBar();

這是同時觸發的方式

1
2
3
4
5
6
7
8
// 寫法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 寫法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
  1. await命令只能用在async函数之中,如果用在普通函数,就会报错。

延伸閱讀

這部分深入理解比較,修行在個人
與其他異步處理方法的比較

以及【按顺序完成异步操作
我擷取其中程式碼,我認為可以激盪一下:

經常遇到一组異步操作,需要按照顺序完成。
比如,依次遠程讀取一组 URL,然后按照讀取的顺序输出结果。
async寫法:

1
2
3
4
5
6
async function logInOrder(urls) {
for (const url of urls) {
const response = await fetch(url);
console.log(await response.text());
}
}

寫法大大簡化
但是,是符合次序撈取遠程等待撈取資料,再等待次序輸出結果!
要是可以同時撈取資料,仍然是依次序輸出結果?可以大大改善效率!
async寫法:

1
2
3
4
5
6
7
8
9
10
11
12
async function logInOrder(urls) {
// 同時讀取遠程URL
const textPromises = urls.map(async url => {
const response = await fetch(url);
return response.text();
});

// 按次序输出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}

是否感受到async的強大與易讀!

對於 await 背後的操作方式,還是不太了解
我這邊有寫一個【jsbin連結】玩玩看

Ref:

MDN web docs-async

unsplash-logoIrvan Smith

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×