async函數返回一個Promise對象,可使用then方法添加回調函數。當函數執行的時候,一旦遇到await就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。html
舉一個栗子:git
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 函數有多種形式es6
// 函數聲明
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 () => {};
複製代碼
注意⚠️:github
好了,若是你只是想了解async函數和await怎麼用的,到這裏就能夠了。shell
那麼json
一句話,它就是Generator函數的語法糖。promise
而Generator的實現不得不依賴於Iterator接口。bash
而Iterator的實現思想又來源於單向鏈表數據結構
因此咱們先來講說單向鏈表多線程
wiki:鏈表(Linked list)是一種常見的基礎數據結構,是一種線性表,可是並不會按線性的順序儲存數據,而是在每個節點裏存到下一個節點的指針(Pointer)。因爲沒必要須按順序儲存,鏈表在插入的時候能夠達到 o(1)的複雜度,比另外一種線性表順序錶快得多,可是查找一個節點或者訪問特定編號的節點則須要 o(n)的時間,而順序表響應的時間複雜度分別是 o(logn)和 o(1)。
因此單向鏈表的優勢就是:
簡單來講,單向鏈表是鏈表中最簡單的一種,它包含兩個域,一個信息域,一個指針域。這個連接指向列表中的下一個節點,而最後一個節點則指向null
單向鏈表的實現能夠看這篇文章: es6實現單向鏈表
Iterator是一種接口,爲各類不一樣的數據結構(原生支持Array Object Map Set)提供統一的訪問機制。任何部署了Iterator接口的數據結構均可以完成遍歷操做。
每一次調用next方法,都會返回數據結構的當前成員的信息。具體來講,就是返回一個包含value和done兩個屬性的對象。其中,value屬性是當前成員的值,done屬性是一個布爾值,表示遍歷是否結束。
根據以上的規則,咱們來簡單實現一個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方法返回值的規格能夠描述以下。
interface Iterable {
[Symbol.iterator]() : Iterator,
}
interface Iterator {
next(value?: any) : IterationResult,
}
interface IterationResult {
value: any,
done: boolean,
}
複製代碼
ES6規定,默認的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接口的數據結構呢
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函數有兩個特徵。
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函數內部的每個狀態。 與普通函數不一樣的是。調用Generator函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象。
根據以前
任意一個對象的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是單線程的,通俗的講就是代碼一行行的執行唄。但是要是遇到了下面這種狀況呢?
若是採用多線程,同時運行多個任務,那極可能就是下面這樣。
那麼要怎麼解決這個問題呢?這時候就用到了Event Loop。
Event Loop是一個程序結構,用於等待和發送消息和事件
簡單說,就是在程序中設置兩個線程:一個負責程序自己的運行,稱爲"主線程";另外一個負責主線程與其餘進程(主要是各類I/O操做)的通訊,被稱爲"Event Loop線程"(能夠譯爲"消息線程")。
上圖主線程的綠色部分,仍是表示運行時間,而橙色部分表示空閒時間。每當遇到I/O的時候,主線程就讓Event Loop線程去通知相應的I/O程序,而後接着日後運行,因此不存在紅色的等待時間。等到I/O程序完成操做,Event Loop線程再把結果返回主線程。主線程就調用事先設定的回調函數,完成整個任務。
咱們怎麼實現異步呢?
比較傳統的幾種方法:
fs.readFile('/etc/passwd', 'utf-8', function (err, data) {
if (err) throw err;
console.log(data);
});
複製代碼
上面代碼中,readFile函數的第三個參數,就是回調函數,也就是任務的第二段。等到操做系統返回了/etc/passwd這個文件之後,回調函數纔會執行。
可是回調函數很容易出現一個問題
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) {
// ...
});
});
});
複製代碼
這種時候就出現了多重嵌套,因爲多個文件造成了強耦合,只要有一個操做須要修改,它的上層回調函數和下層回調函數,可能都要跟着修改。這種狀況就稱爲"回調函數地獄"
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,語義變得不清楚。
那麼,有沒有更好的寫法呢?
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());
};
複製代碼
而後,咱們能夠手動執行
var g = gen();
g.next().value.then(function(data){
g.next(data).value.then(function(data){
g.next(data);
});
});
複製代碼
手動執行其實就是用then方法,層層添加回調函數。理解了這一點,就能夠寫出一個自動執行器。
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 函數還沒執行到最後一步,next函數就調用自身,以此實現自動執行。
說了這麼多,開始切入正題
// 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函數就是自帶了執行器 的Generator函數的語法糖,咱們只須要把'*'換成async,把yield換成await就能夠了。