async怎么用
async函數返回一個Promise對象,可以使用then方法添加回調函數。當函數執(zhí)行的時候,一旦遇到await就會先返回,等到異步操作完成,再接著執(zhí)行函數體內后面的語句。
舉一個栗子:
let timeout = ms => {
console.log('a')
return new Promise(resolve => {
console.log('b')
setTimeout(resolve, ms)
})
}
let asyncPrint = async (value, ms) => {
await timeout(ms)
console.log(value)
return `${value} world`
}
asyncPrint('hello', 500).then(_ => {
console.log(_)
})//先依次輸出 a b 然后500毫秒后輸出hello 和 hello world
async 函數有多種形式
// 函數聲明
async function foo() {}
// 函數表達式
const foo = async function () {};
// 對象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭頭函數
const foo = async () => {};
注意??:
- async函數返回的是promise對象,所以可以用then接受函數內部return的數據
- await命令后面一般跟著Promise對象,會等著返回Promise的結果,然后執(zhí)行后面的。如果不是Promise對象,比如是123,這時等于 return 123。
- await命令目前只允許存在與async函數里面
好了,如果你只是想了解async函數和await怎么用的,到這里就可以了。
=======================================
我是分割線
那么
async到底是什么
一句話,它就是Generator函數的語法糖。
而Generator的實現不得不依賴于Iterator接口。
而Iterator的實現思想又來源于單向鏈表
所以我們先來說說單向鏈表
單向鏈表
wiki:鏈表(Linked list)是一種常見的基礎數據結構,是一種線性表,但是并不會按線性的順序儲存數據,而是在每一個節(jié)點里存到下一個節(jié)點的指針(Pointer)。由于不必須按順序儲存,鏈表在插入的時候可以達到 o(1)的復雜度,比另一種線性表順序表快得多,但是查找一個節(jié)點或者訪問特定編號的節(jié)點則需要 o(n)的時間,而順序表響應的時間復雜度分別是 o(logn)和 o(1)。
所以單向鏈表的優(yōu)點就是:
- 無需預先分配內存
- 插入/刪除節(jié)點不影響其他節(jié)點,效率高(典型的栗子:dom操作)
簡單來說,單向鏈表是鏈表中最簡單的一種,它包含兩個域,一個信息域,一個指針域。這個鏈接指向列表中的下一個節(jié)點,而最后一個節(jié)點則指向null

單向鏈表的實現可以看這篇文章:
es6實現單向鏈表
Iterator(遍歷器)
Iterator是一種接口,為各種不同的數據結構(原生支持Array Object Map Set)提供統(tǒng)一的訪問機制。任何部署了Iterator接口的數據結構都可以完成遍歷操作。
Iterator的作用:
- 為各種數據結構提供一個統(tǒng)一的、簡便的訪問接口
- 使數據結構的成員能夠按某種次序排列
- 依靠Iterator,可以實現for...of循環(huán)
Iterator的遍歷過程
- 創(chuàng)建一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。
- 第一次調用指針對象的next方法,可以將指針指向數據結構的第一個成員。
- 第二次調用指針對象的next方法,指針就指向數據結構的第二個成員。
- 不斷調用指針對象的next方法,直到它指向數據結構的結束位置。
每一次調用next方法,都會返回數據結構的當前成員的信息。具體來說,就是返回一個包含value和done兩個屬性的對象。其中,value屬性是當前成員的值,done屬性是一個布爾值,表示遍歷是否結束。
根據以上的規(guī)則,我們來簡單實現一個next方法
let makeIterator = array => {
let nextIndex = 0;
return {
next: _ => nextIndex < array.length ?
{ value: array[nextIndex++], done: false }:
{ value: undefined, done: true }
}//返回的是一個指針對象
}
let it = makeIterator(['a','b','c','d'])
it.next()
it.next()
it.next()
it.next()
it.next()
it.next()
//next方法用來移動指針,第一次調用會指向a,并且返回一個對象,表示當前數據成員的信息,這個對象具有value和done兩個屬性,第二次調用指向b。。。
如果使用 TypeScript 的寫法,遍歷器接口(Iterable)、指針對象(Iterator)和next方法返回值的規(guī)格可以描述如下。
interface Iterable {
[Symbol.iterator]() : Iterator,
}
interface Iterator {
next(value?: any) : IterationResult,
}
interface IterationResult {
value: any,
done: boolean,
}
接口部署
ES6規(guī)定,默認的Iterator接口部署在數據結構的Symbol.iterator屬性上(symbol是什么),就是說,一個數據結構只要具有Symbol.iterator屬性,就可以認為是可遍歷的(Iterable)。
再舉個栗子:
let arr = ['a','b','c']
let iter = arr[Symbol.iterator]()
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
那么原生有哪些具備Iterator接口的數據結構呢
- Array
- Map
- Set
- String
- arguments對象
- NodeList 對象
調用Iterator接口的場合
- 解構賦值
- 擴張運算符
- Array.form()
- Map(), Set()(比如new Map([['a',1],['b',2]]))
- Promise.all()
- Promise.race()
- ==for...of==
const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v); // red green blue
}
const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
for(let v of obj) {
console.log(v); // red green blue
}
- ==Generator函數==
==========================================我是分割線
什么是Generator函數
形式上,Generator函數有兩個特征。
- function關鍵字與函數名之間有一個*號
- 函數體內部使用yield表達式,定義不同的內部狀態(tài)
function* myGenerator() {
yield 'hi'
yield 'lueluelue'
return 'byebye'
}
let myG = myGenerator()
myG.next() //{value: "hi", done: false}
myG.next() //{value: "lueluelue", done: false}
myG.next() //{value: "byebye", done: true}
myG.next() //{value: undefined, done: true}
通過上面的代碼,可以得出,Generator函數是一個指針對象生成函數,返回的指針對象,可以依次遍歷Generator函數內部的每一個狀態(tài)。
與普通函數不同的是。調用Generator函數后,該函數并不執(zhí)行,返回的也不是函數運行結果,而是一個指向內部狀態(tài)的指針對象。
Generator函數與Iterator接口的關系
根據之前
任意一個對象的Symbol.iterator方法,等于該對象的遍歷器生成函數,調用該函數會返回該對象的一個指針對象。
又因為Generator函數就是遍歷器生成函數,因此可以把Generator函數賦值給對象的Symbol.iterator屬性,從而使該對象具有Iterator接口。
比如:
let myIterable = {}
myIterable[Symbol.iterator] = function* (){
yield 1
yield 2
yield 3
}
[...myIterable] // 1,2,3
那么aynsc和Generator又是什么關系呢?
回答這個問題之前,我們需要知道什么是異步。
=============================================我是分割線
異步
我們都知道,js是單線程的,通俗的講就是代碼一行行的執(zhí)行唄。可是要是遇到了下面這種情況呢?

上圖的綠色部分是程序的運行時間,紅色部分是等待時間。可以看到,由于I/O操作很慢,所以這個線程的大部分運行時間都在空等I/O操作的返回結果。
那為什么不用多線程呢?
如果采用多線程,同時運行多個任務,那很可能就是下面這樣。

可以看到,多線程不僅占用多倍的系統(tǒng)資源,也閑置多倍的資源,這顯然不合理。
那么要怎么解決這個問題呢?這時候就用到了Event Loop。
Event Loop是一個程序結構,用于等待和發(fā)送消息和事件
簡單說,就是在程序中設置兩個線程:一個負責程序本身的運行,稱為"主線程";另一個負責主線程與其他進程(主要是各種I/O操作)的通信,被稱為"Event Loop線程"(可以譯為"消息線程")。

上圖主線程的綠色部分,還是表示運行時間,而橙色部分表示空閑時間。每當遇到I/O的時候,主線程就讓Event Loop線程去通知相應的I/O程序,然后接著往后運行,所以不存在紅色的等待時間。等到I/O程序完成操作,Event Loop線程再把結果返回主線程。主線程就調用事先設定的回調函數,完成整個任務。
這種運行方式稱為"異步模式"
我們怎么實現異步呢?
比較傳統(tǒng)的幾種方法:
- 回調函數
- 事件監(jiān)聽
- 發(fā)布/訂閱
- Promise 對象
比如回調函數(callback)
fs.readFile('/etc/passwd', 'utf-8', function (err, data) {
if (err) throw err;
console.log(data);
});
上面代碼中,readFile函數的第三個參數,就是回調函數,也就是任務的第二段。等到操作系統(tǒng)返回了/etc/passwd這個文件以后,回調函數才會執(zhí)行。
但是回調函數很容易出現一個問題
fs.readFile(fileA, 'utf-8', function (err, data) {
//do something
fs.readFile(fileB, 'utf-8', function (err, data) {
//do something
fs.readFile(fileC, 'utf-8', function (err, data) {
// ...
});
});
});
這種時候就出現了多重嵌套,由于多個文件形成了強耦合,只要有一個操作需要修改,它的上層回調函數和下層回調函數,可能都要跟著修改。這種情況就稱為"回調函數地獄"
Promise就很好的解決了這個問題
var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) return reject(error);
resolve(data);
});
});
};
readFile(fileA)
.then(function (data) {
console.log(data.toString());
})
.then(function () {
return readFile(fileB);
})
.then(function (data) {
console.log(data.toString());
})
.then(function () {
return readFile(fileC);
})
.then(function (data) {
console.log(data.toString());
})
.catch(function (err) {
console.log(err);
});
可以看到使用了Promise以后,異步任務看的比較的清楚了,但又有了一個問題,那就是代碼顯得很冗余,一眼看去是一堆的then,語義變得不清楚。
那么,有沒有更好的寫法呢?
答案是Generator函數
var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) return reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
console.log(f1.toString());
var f2 = yield readFile('/etc/shells');
console.log(f2.toString());
};
然后,我們可以手動執(zhí)行
var g = gen();
g.next().value.then(function(data){
g.next(data).value.then(function(data){
g.next(data);
});
});
手動執(zhí)行其實就是用then方法,層層添加回調函數。理解了這一點,就可以寫出一個自動執(zhí)行器。
function run(gen){
var g = gen();
function next(data){
var result = g.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
run(gen);
上面代碼中,只要 Generator 函數還沒執(zhí)行到最后一步,next函數就調用自身,以此實現自動執(zhí)行。
說了這么多,開始切入正題
那么到底什么是 aynsc呢
// Generator
run(function*() {
const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
console.log(res1);
const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
console.log(res2);
});
// async/await
const readFile = async ()=>{
const res1 = await readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
console.log(res1);
const res2 = await readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
console.log(res2);
return 'done';
}
const res = readFile();
答案就是 async函數就是自帶了執(zhí)行器 的Generator函數的語法糖,我們只需要把'*'換成async,把yield換成await就可以了。