什麼是異步?html
所謂異步,簡單來講就是異步任務(不會立刻就完成的任務);可是js不會等待你這個任務完成,而是直接執行下邊的任務;等到你上邊的任務完成以後纔會去執行相應的邏輯。好比js讀取文件就是異步的過程。node
一、回調函數git
場景: 讀取一個文件github
let fs = require('fs')
fs.readFile('./1.txt', 'utf8', function(err, data){
// 回調的特色是第一個參數通常爲錯誤對象
if (err) { // 若是err有值說明程序出錯了
console.log(err)
} else { // 不然表示成功獲取到數據data
console.log(data)
}
})
複製代碼
固然回調函數也有它的缺點:編程
funnction readFile (fileName) {
fs.readFile(fileName, 'utf8', function (data) {
if (err) {
console.log(err)
} else {
console.log(data)
}
})
}
try {
readFile('./1.txt')
} catch (e) { // 若是上邊讀取文件出錯,獲取不到錯誤信息
console.log('err', e)
}
複製代碼
// readFile 方法中沒法返回讀取到文件的內容(data)
複製代碼
fs.readFile('./template.txt', 'utf8', function (err, template) {
fs.readFile('./data.txt', 'utf8', function (err, data) {
console.log(template, data)
})
})
// 這樣的代碼稱爲惡魔金字塔;且有如下問題
// 一、代碼很是難看
// 二、難以維護
// 三、效率比較低,由於它們是串行的;一次只能請求一個文件
複製代碼
二、事件發佈訂閱(爲了解決回調嵌套的問題)promise
let EventEmitter = require('events')
// nodejs核心模塊之一;包含兩個核心方法 on >> 表示註冊監聽 emit >> 表示發射事件
let eve = new EventEmitter()
let html = {} // 存放頁面模板和數據
eve.on('onloading', function (key, value) {
html[key] = value
if (Object.keys(html).length == 2) {
console.log(html)
}
})
fs.readFile('./template.txt', 'utf8', function (err, template) {
eve.emit('onloading', template) // 觸發onloading事件,執行事件的回調函數向html中填入模板
})
fs.readFile('./data.txt', 'utf8', function (err, template) {
eve.emit('onloading', template) // 觸發onloading事件,執行事件的回調函數向html中填入數據
})
複製代碼
三、哨兵變量 (一樣處理回調嵌套的問題) 事件發佈訂閱已經能夠解決回調嵌套的問題,可是還須要引入events模塊; 利用哨兵變量同樣能夠解決回調嵌套的問題,且不須要引入其餘模塊bash
// 定義一個哨兵函數來處理
function done (key, value) {
html[key] = value
if (Object.keys(html).length == 2) {
console.log(html)
}
}
fs.readFile('./template.txt', 'utf8', function (err, template) {
done('template', template)
})
fs.readFile('./data.txt', 'utf8', function (err, template) {
done('data', data)
})
// 能夠封裝一個高階函數去生成哨兵函數
function render (length, cb) {
let htm = {}
return function (key, value) {
html[key] = value
if (Object.keys(html).length == length) {
cb(html)
}
}
}
let done = render(2, function (html) {
console.log(html)
})
複製代碼
四、Promise 上述方法都是用回調函數來處理異步;咱們的目標是把異步往同步的方向靠攏服務器
let promise1 = new Promise(function (resolve, reject) {
fs.readFile('./1.txt', 'utf8', function (err, data) {
resolve(data)
})
})
promise1
.then(function (data) {
console.log(data)
})
複製代碼
五、生成器Generator 當咱們在調用一個函數的時候,它並不會立刻執行,而是須要咱們手動的去執行迭代操做(next方法);簡單來講,調用生成器函數會返回一個迭代器,能夠用迭代器來執行遍歷每一箇中斷點(yield) 調用next方法會有返回值value,是生成器函數對外輸出的數據;next方法還能夠接受參數,是向生成器函數內部輸入的數據async
// 方法名前邊加*就是生成器函數
function *foo () {
var index = 0;
while (index < 2) {
yield index++; //暫停函數執行,並執行yield後的操做
}
}
var bar = foo(); // 返回的實際上是一個迭代器
console.log(bar.next()); // { value: 0, done: false }
console.log(bar.next()); // { value: 1, done: false }
console.log(bar.next()); // { value: undefined, done: true }
複製代碼
function readFile (filaName) {
return new Promise(function (resolve, reject) {
fs.readFile(filename, function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
}
function *read() {
let template = yield readFile('./template.txt')
let data = yield readFile('./data.txt')
return {
template: template,
data: data
}
}
// 生成迭代器r1
let r1 = read()
let templatePromise = r1.next().value
templatePromise.then(function(template) {
// 將獲取到的template的內容傳遞給生成器函數
let dataPromsie = r1.next(template).value
dataPromise.then(function(data) {
//最後一次執行next傳入data的值;最後返回{template, data}
let result = r1.next(data).value
console.log(result)
})
})
複製代碼
生成器 + promise的實現已經有了一些同步的樣子; 藉助一些工具(co),能夠優雅的編寫上述的代碼
//實現 co 方法
// 參數是一個生成器函數
function co (genFn) {
let r1 = genFn()
return new Promise(function(resolev, reject) {
!function next(lastVal) {
let p1 = r1.next(lastVal)
if (p1.done) {
resolve(p1.value)
} else {
p1.value.then(next, reject)
}
}()
})
}
如今獲取上邊的result能夠這樣來取
co(read).then(function(result) {
console.log(result)
})
複製代碼
六、Async/await Async實際上是一個語法糖,它的實現就是將Generator函數和自動執行器(co),包裝在一個函數中
async function read() {
let template = await readFile('./template.txt');
let data = await readFile('./data.txt');
return template + '+' + data;
}
// 等同於
function read(){
return co(function*() {
let template = yield readFile('./template.txt');
let data = yield readFile('./data.txt');
return template + '+' + data;
});
}
複製代碼
結論:異步編程發展的目標就是讓異步邏輯的代碼看起來像同步同樣;發展到Async/await;是處理異步編程探索的一個里程碑。