因爲JavaScript是單線程的一門腳本語言(主線程是單線程)node
因此異步問題是個讓人常頭疼的問題segmentfault
咱們來看一下常見的傳統解決方案數組
回調函數是一種最多見 最傳統的方式 相似的這種promise
// node 的文件讀取 let fs = require('fs'); fs.readFile('./test1.js','utf8',function(err,data){ console.log(data) })
這樣咱們能夠在回調函數裏拿到文件的內容,然而這樣有一個問題, 要是我要讀取多個文件,每個讀取的文件都要依賴前一個讀取文件的內容瀏覽器
好比 我test1.js的內容是test2的路徑異步
那麼就要這樣寫async
let fs = require('fs'); fs.readFile('./test1.js','utf8',function(err,data){ fs.readFile(data,'utf8',function(err,data){ console.log(data) }) })
要是100個 1000個文件呢 ? 因爲異步調用沒法用try{}catch 捕獲 萬一中間讀取失敗了一次又該怎麼作? 難道每一個函數體內都if(err) 一下? 這種方式難以維護 也就是咱們常說的回調地獄函數
我如今有這樣一種需求 我須要在不一樣的文件裏讀取不一樣的內容, 等多個文件的內容都讀取完畢再一塊兒輸出ui
let fs = require('fs'); let result = {}; fs.readFile('./test1.js','utf8',function(err,data){ result.test1 = data fs.readFile('./test2','utf8',function(err,data){ result.test2 = data console.log(result) }) })
用回調方式會帶來什麼問題? 需求: 這些異步請求沒有依賴關係 我須要同時發起 而不是等待上一次讀取的結果this
如今咱們來聊聊 訂閱發佈模式
訂閱發佈模式定義了一種一對多的依賴關係,讓多個訂閱者對象同時監聽某一個主題對象。這個主題對象在自身狀態變化時,會通知全部訂閱者對象,使它們可以自動更新本身的狀態。 通俗點就事說, 我把我要操做的事放入一個待執行的隊列裏, 等達到某一個條件,待執行隊列依次執行,那麼上代碼
let fs = require('fs'); let result = {}; class Publish { constructor() { this.list = [] }; on(fn){ this.list.push(fn) }; emit(string){ alert(string) if (Object.keys(result).length == 2) { this.list.forEach(fn => { fn() }) } } } let p = new Publish() p.on(function () { console.log(result) }) fs.readFile('./test1.js', 'utf8', function (err, data) { result.test1 = data p.emit('已經讀取到test1的文件') }) fs.readFile('./test2', 'utf8', function (err, data) { result.test2 = data p.emit('已經讀取到test2的文件') })
原理其實也就是回調函數
問題:發佈訂閱跟觀察者模式有什麼區別??
好在咱們有了Promise這個類 關於Promise的文章有不少 你們自行能夠搜索一下
咱們來看下Promise A+ 規範
那根據這個規範咱們簡單的寫一遍promise的源碼吧
咱們來定義2個文件
Promise.js和require.js
//require.js let Promise = require('./promise.js') let p = new Promise((resolve, reject) => { setTimeout(()=>{ resolve(100) },100) }) p.then(function(data){ console.log(data) },function(e){ console.log(e) })
//promise.js class Promise { constructor(executor){ // promise的三個狀態 this.state = 'pending' this.value = undefined this.reason = undefined // 有可能調用then的時候 並無resolve或者reject 因此這裏用來存放then以後要作的事 this.onResolvedCallbacks = [] this.onRejectedCallbacks = [] const resolve = (value) => { // 咱們須要判斷resolve出來的值是否仍是一個promise if(value instanceof Promise){ return value.then(resolve,reject) } // promiseA+ 規範要求這麼寫 setTimeout(()=>{ if (this.state === 'pending') { this.state = 'resolved' this.value = value // 把保存起來的函數一一執行而後結果傳給下一個 this.onResolvedCallbacks.forEach( fn => { return fn(value) }) } }) } const reject = (reason) => { setTimeout(()=>{ if (this.state === 'pending') { this.state = 'rejected' this.reason = reason this.onRejectedCallbacks.forEach(fn => { return fn(reason) }) } }) } try { executor(resolve,reject) } catch (error) { reject(error) } } then(onFulfilled,onRejected){ // new 的時候立刻執行executor ----> 就是(resolve,reject)=>{ }() 拿到resolve跟reject 而後作狀態判斷該調用哪一個 onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : function (value) { return value }; onRejected = typeof onRejected == 'function' ? onRejected : function (value) { throw value }; let promise2 = new Promise((resolve,reject)=>{ if (this.state === 'resolved'){ //onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1]. // 規範上要這麼作 防止直接resolve 同步調用then 這個時候promise2不存在報錯 // 執行順序參考瀏覽器事件環 哪天有空單獨寫一篇 setTimeout(()=>{ // 由於onFulfilled都是異步調用 因此不能在new Promise的時候捕獲到 try { let x = onFulfilled(this.value) // then成功的回調 resolvePromise(promise2, x, resolve, reject) } catch (error) { reject(error) } }) } if (this.state === 'rejected'){ setTimeout(() => { try { let x = onRejected(this.reason) // 失敗的回調 resolvePromise(promise2, x, resolve, reject) } catch (error) { reject(error) } }) } if (this.state === 'pending'){ // 若是executor是個異步方法 那麼會先調用then 因此這裏把成功回調跟失敗的回調都存起來 this.onResolvedCallbacks.push((value)=>{ try { let x = onFulfilled(value) resolvePromise(promise2, x, resolve, reject) } catch (error) { console.log(error) reject(error) } }) this.onRejectedCallbacks.push((reason)=>{ try { let x = onRejected(reason) resolvePromise(promise2, x, resolve, reject) } catch (error) { reject(error) } }) } }) // then返回一個promise return promise2 } catch(onRejected){ return this.then(null, onRejected); } static all(promises){ return new Promise(function (resolve, reject) { let result = []; let count = 0; for (let i = 0; i < promises.length; i++) { promises[i].then(function (data) { result[i] = data; if (++count == promises.length) { resolve(result); } }, function (err) { reject(err); }); } }); } } const resolvePromise = (promise2, x, resolve, reject)=>{ // promise2 跟 then的成功回調返回值有多是同一個值 if(promise2 === x){ return reject(new TypeError('報錯 循環引用了')) } let then,called; // 要麼對象要麼函數 if(x !== null&&((typeof x === 'object' || typeof x === 'function')) ){ try { then = x.then // 有多是getter定義的會報錯 // then 有多是個函數或者普通值 if(typeof then === 'function'){ // 若是then是個函數的話 就認爲它是個promise then.call(x,function(){ if(called) return called = true resolvePromise(promise2, y, resolve, reject); },function(error){ if (called) return called = true reject(error) }) }else{ resolve(x) } } catch (error) { if (called) return called = true reject(error) } }else{ // x是個普通值 resolve(x) } } module.exports = Promise
執行require.js的結果是
這樣咱們就實現了一個promise 是否是很棒棒? 如今咱們能夠promise.then 鏈式調用了 而後用catch作統一錯誤處理 解決了上面錯誤捕獲的問題 還有沒有更好的方法? 固然有!
在講async await 以前 咱們先講一下 生成器
**當你在執行一個函數的時候,你能夠在某個點暫停函數的執行,而且作一些其餘工做,而後再返回這個函數繼續執行, 甚至是攜帶一些新的值,而後繼續執行。
上面描述的場景正是JavaScript生成器函數所致力於解決的問題。當咱們調用一個生成器函數的時候,它並不會當即執行, 而是須要咱們手動的去執行迭代操做(next方法)。也就是說,你調用生成器函數,它會返回給你一個迭代器。迭代器會遍歷每一箇中斷點。
next 方法返回值的 value 屬性,是 Generator 函數向外輸出數據;next 方法還能夠接受參數
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 }
Generator函數的標誌就是function關鍵詞後連綴一個'*' 配合yield 暫停函數 返回的是一個迭代器 每次執行next的時候 停在yield
咱們都見過類數組結構吧
let likeArray = { 0: 1, 1: 2, 2: 3, length: 3 } let arr = [...likeArray] //執行這段代碼會報錯 報錯信息likeArray is not iterable likeArray是不可枚舉的 那麼咱們若是想實現這樣的類數組轉爲數組 怎麼辦呢
咱們先看一下函數裏的argument跟類數組有什麼區別
function(){ console.log(argument)}
咱們改下一下類數組結構
let likeArray = { 0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator](){ return { next() { return { value: 1, done: false } } } } } //在執行 let arr = [...likeArray] 控制檯報錯FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 感受是否是有點像那麼回事了 咱們再改寫 let likeArray = { 0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator](){ let index = 0 let self = this return { next() { return { done: self.length === index value: self[index++], } } } } } // 輸出[1,2,3] 只有在done是false的時候表示迭代完成 就再也不繼續執行了 value是每次迭代返回的值 再改寫一下 let likeArray = { 0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator]:function*() { let index = 0; while (index !== this.length) { yield this[index++] } } } console.log([...likeArray]) //[1,2,3] 調用返回一個迭代器 ... 每次調用迭代器的next方法 返回{value,done}
生成器能夠配合node.js中的co, 藉助於Promise,你可使用更加優雅的方式編寫非阻塞代碼。
例子:
let fs = require('fs'); function readFile(filename) { 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 + '+' + data; } co(read).then(function (data) { console.log(data); }, function (err) { console.log(err); });
有了上面的基礎 async/await 更加容易明白了
async/await的優勢有
1.內置執行器
2.更好的語義
3.更廣的適用性
let fs = require('fs'); function readFile(filename) { return new Promise(function (resolve, reject) { fs.readFile(filename, 'utf8', function (err, data) { if (err) reject(err); else resolve(data); }) }) } async function read() { let template = await readFile('./template.txt'); let data = await readFile('./data.txt'); return template + '+' + data; } let result = read(); result.then(data=>console.log(data));
能夠直接await 一個promise 使得異步代碼執行看起來像同步同樣 更優雅
async 函數的實現,就是將 Generator 函數和自動執行器,包裝在一個函數裏。
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 用async/await寫異步代碼 更方便維護第一次寫文章 寫的很差多多包涵 畢竟不少東西都是站在前任人的肩膀上直接拿過來的**