關注公衆號「 執鳶者」,回覆「 書籍」獲取大量前端學習資料,回覆「 前端視頻」獲取大量前端教學視頻,回覆「 異步」獲取本節總體思惟導圖。
本節主要闡述六種異步方案:回調函數、事件監聽、發佈/訂閱、Promise、Generator和Async。其中重點是發佈/訂閱、Promise、Async的原理實現,經過對這幾點的瞭解,但願咱們前端切圖仔可以在修煉內功的路上更進一步。
異步編程的最基本方法,把任務的第二段單獨寫在一個函數裏面,等到從新執行這個任務的時候,就直接調用這個函數。
function sleep(time, callback) { setTimeout(() => { // 一些邏輯代碼 callback(); }, time); }
異步任務的執行不取決於代碼的執行順序,而取決於某個事件是否發生。
dom.addEventListener('click', () => { console.log('dom被點擊後觸發!!!'); })
發佈/訂閱模式在觀察者模式的基礎上,在目標和觀察者之間增長一個調度中心。訂閱者(觀察者)把本身想要訂閱的事件註冊到調度中心,當該事件觸發的時候,發佈者(目標)發佈該事件到調度中心,由調度中心統一調度訂閱者註冊到調度中心的處理代碼。
Promise 是異步編程的一種解決方案,是爲解決回調函數地獄這個問題而提出的,它不是新的語法功能,而是一種新的寫法,容許將回調函數的嵌套改成鏈式調用。
const promise = new Promise((resolve, reject) => { if (/*若是異步成功*/) { resolve(value); } else { reject(error); } }); promise.then((value) => { // ...success }, (reason) => { // ...failure })
Generator 函數是ES6提供的一種異步編程解決方案,語法行爲與傳統函數徹底不一樣。其最大特色是能夠控制函數的執行。
function* genF() { yield 'come on!'; yield 'Front End Engineer'; return 'goood'; } const gF = genF(); gF.next();// {value: "come on!", done: false} gF.next();// {value: "Front End Engineer", done: false} gF.next();// {value: "goood", done: true} gF.next();// {value: undefined, done: true}
ES2017 標準引入了async函數,使得異步操做變得更加方便。簡言之,該函數就是Generator函數的語法糖。
async function asyncFun() { await func1() await func2(); return '666'; } function func1() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('888') }, 100); }).then((value) => { console.log(value); }); } function func2() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('777') }); }).then((value) => { console.log(value); }); } asyncFun().then((value) => { console.log(value); }); // 888 // 777 // 666
不論是實際開發中仍是面試過程當中,各位老鐵們對Promise確定不會陌生,下面就讓咱們一塊兒來嘮一嘮Promsie的實現原理,根據PromiseA+規範來進行實現,而後對其相關的靜態方法(Promise.resolve()、Promise.reject()、Promise.all()、Promise.race())和實例方法(Promise.prototype.catch()、Promise.prototype.finally())進行實現。
首先用一幅圖來展現一下我考慮實現這個函數的思路吧。
人家有相關標準,咱們就要遵照,畢竟遵紀守法纔是好公民,如今只能硬着頭皮把這個標準過一遍。
下面就是基於Promise/A+規範實現的代碼,已經通過promises-aplus-tests庫進行了驗證。
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; /** * Promise構造函數 * excutor: 內部同步執行的函數 */ class Promise { constructor(excutor) { const self = this; self.status = PENDING; self.onFulfilled = [];// 成功的回調 self.onRejected = [];// 失敗的回調 // 異步處理成功調用的函數 // PromiseA+ 2.1 狀態只能由Pending轉爲fulfilled或rejected;fulfilled狀態必須有一個value值;rejected狀態必須有一個reason值。 function resolve(value) { if (self.status === PENDING) { self.status = FULFILLED; self.value = value; // PromiseA+ 2.2.6.1 相同promise的then能夠被調用屢次,當promise變爲fulfilled狀態,所有的onFulfilled回調按照原始調用then的順序執行 self.onFulfilled.forEach(fn => fn()); } } function reject(reason) { if (self.status === PENDING) { self.status = REJECTED; self.reason = reason; // PromiseA+ 2.2.6.2 相同promise的then能夠被調用屢次,當promise變爲rejected狀態,所有的onRejected回調按照原始調用then的順序執行 self.onRejected.forEach(fn => fn()); } } try { excutor(resolve, reject); } catch (e) { reject(e); } } then(onFulfilled, onRejected) { // PromiseA+ 2.2.1 onFulfilled和onRejected是可選參數 // PromiseA+ 2.2.5 onFulfilled和onRejected必須被做爲函數調用 // PromiseA+ 2.2.7.3 若是onFulfilled不是函數且promise1狀態是fulfilled,則promise2有相同的值且也是fulfilled狀態 // PromiseA+ 2.2.7.4 若是onRejected不是函數且promise1狀態是rejected,則promise2有相同的值且也是rejected狀態 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }; const self = this; const promise = new Promise((resolve, reject) => { const handle = (callback, data) => { // PromiseA+ 2.2.4 onFulfilled或者onRejected須要在本身的執行上下文棧裏被調用,因此此處用setTimeout setTimeout(() => { try { // PromiseA+ 2.2.2 若是onFulfilled是函數,則在fulfilled狀態以後調用,第一個參數爲value // PromiseA+ 2.2.3 若是onRejected是函數,則在rejected狀態以後調用,第一個參數爲reason const x = callback(data); // PromiseA+ 2.2.7.1 若是onFulfilled或onRejected返回一個x值,運行這[[Resolve]](promise2, x) resolvePromise(promise, x, resolve, reject); } catch (e) { // PromiseA+ 2.2.7.2 onFulfilled或onRejected拋出一個異常e,promise2必須以e的理由失敗 reject(e); } }) } if (self.status === PENDING) { self.onFulfilled.push(() => { handle(onFulfilled, self.value); }); self.onRejected.push(() => { handle(onRejected, self.reason); }) } else if (self.status === FULFILLED) { setTimeout(() => { handle(onFulfilled, self.value); }) } else if (self.status === REJECTED) { setTimeout(() => { handle(onRejected, self.reason); }) } }) return promise; } } function resolvePromise(promise, x, resolve, reject) { // PromiseA+ 2.3.1 若是promise和x引用同一對象,會以TypeError錯誤reject promise if (promise === x) { reject(new TypeError('Chaining Cycle')); } if (x && typeof x === 'object' || typeof x === 'function') { // PromiseA+ 2.3.3.3.3 若是resolvePromise和rejectPromise都被調用,或者對同一個參數進行屢次調用,那麼第一次調用優先,之後的調用都會被忽略。 let used; try { // PromiseA+ 2.3.3.1 let then be x.then // PromiseA+ 2.3.2 調用then方法已經包含了該條(該條是x是promise的處理)。 let then = x.then; if (typeof then === 'function') { // PromiseA+ 2.3.3.3若是then是一個函數,用x做爲this調用它。第一個參數是resolvePromise,第二個參數是rejectPromise // PromiseA+ 2.3.3.3.1 若是resolvePromise用一個值y調用,運行[[Resolve]](promise, y) // PromiseA+ 2.3.3.3.2 若是rejectPromise用一個緣由r調用,用r拒絕promise。 then.call(x, (y) => { if (used) return; used = true; resolvePromise(promise, y, resolve, reject) }, (r) => { if (used) return; used = true; reject(r); }) } else { // PromiseA+ 若是then不是一個函數,變爲fulfilled狀態並傳值爲x if (used) return; used = true; resolve(x); } } catch (e) { // PromiseA+ 2.3.3.2 若是檢索屬性x.then拋出異常e,則以e爲緣由拒絕promise // PromiseA+ 2.3.3.4 若是調用then拋出異常,可是resolvePromise或rejectPromise已經執行,則忽略它 if (used) return; used = true; reject(e); } } else { // PromiseA+ 2.3.4 若是x不是一個對象或函數,狀態變爲fulfilled並傳值x resolve(x); } }
按照Promise/A+規範實現了Promise的核心內容,可是其只實現了Promise.prototype.then()方法,那其它方法呢?下面咱們就嘮一嘮其它方法,包括靜態方法(Promise.resolve()、Promise.reject()、Promise.all()、Promise.race())和實例方法(Promise.prototype.catch()、Promise.prototype.finally())。
class Promise { // ... // 將現有對象轉爲 Promise 對象 static resolve(value) { // 若是參數是 Promise 實例,那麼Promise.resolve將不作任何修改、原封不動地返回這個實例。 if (value instanceof Promise) return value; // 參數是一個thenable對象(具備then方法的對象),Promise.resolve方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable對象的then方法。 if (typeof value === 'object' || typeof value === 'function') { try { let then = value.then; if (typeof then === 'function') { return new Promise(then.bind(value)); } } catch (e) { return new Promise((resolve, reject) => { reject(e); }) } } // 參數不是具備then方法的對象,或根本就不是對象,Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。 return new Promise((resolve, reject) => { resolve(value); }) } }
class Promise { // ... // 返回一個新的 Promise 實例,該實例的狀態爲rejected。 static reject(reason) { return new Promise((resolve, reject) => { reject(reason); }) } }
class Promise { // ... // 用於將多個 Promise 實例,包裝成一個新的 Promise 實例。只有全部狀態都變爲fulfilled,p的狀態纔會是fulfilled static all(promises) { const values = []; let resolvedCount = 0; return new Promise((resolve, reject) => { promises.forEach((p, index) => { Promise.resolve(p).then(value => { resolvedCount++; values[index] = value; if (resolvedCount === promises.length) { resolve(values); } }, reason => { reject(reason); }) }) }) } }
class Promise { // ... // 只要有一個實例率先改變狀態,狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給回調函數。 static race(promises) { return new Promise((resolve, reject) => { promises.forEach((p, index) => { Promise.resolve(p).then(value => { resolve(value); }, reason => { reject(reason); }) }) }) } }
class Promise { // ... // 是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。 catch(onRejected) { return this.then(undefined, onRejected); } }
class Promise { // ... // 用於指定無論 Promise 對象最後狀態如何,都會執行的操做。 finally(callback) { return this.then( value => Promise.resolve(callback()).then(() => value), reason => Promise.resolve(callback()).then(() => { throw reason }) ) } }
在開發過程當中經常使用的另外一種異步方案莫過於Async,經過async函數的引入使得異步操做變得更加方便。實質上,async是Generator的語法糖,最大的亮點是async內置執行器,調用後便可自動執行,不像Generator須要調用next()方法才能執行。
這是Async的實現原理,即將Generator函數做爲參數放入run函數中,最終實現自動執行並返回Promise對象。
function run(genF) { // 返回值是Promise return new Promise((resolve, reject) => { const gen = genF(); function step(nextF) { let next; try { // 執行該函數,獲取一個有着value和done兩個屬性的對象 next = nextF(); } catch (e) { // 出現異常則將該Promise變爲rejected狀態 reject(e); } // 判斷是否到達末尾,Generator函數到達末尾則將該Promise變爲fulfilled狀態 if (next.done) { return resolve(next.value); } // 沒到達末尾,則利用Promise封裝該value,直到執行完畢,反覆調用step函數,實現自動執行 Promise.resolve(next.value).then((v) => { step(() => gen.next(v)) }, (e) => { step(() => gen.throw(e)) }) } step(() => gen.next(undefined)); }) }
更加詳細內容能夠參考《圖解23種設計模式》前端
發佈/訂閱模式在觀察者模式的基礎上,在目標和觀察者之間增長一個調度中心。訂閱者(觀察者)把本身想要訂閱的事件註冊到調度中心,當該事件觸發的時候,發佈者(目標)發佈該事件到調度中心,由調度中心統一調度訂閱者註冊到調度中心的處理代碼。
// 發佈訂閱(TypeScript版) interface Publish { registerObserver(eventType : string, subscribe : Subscribe) : void; remove(eventType : string, subscribe ?: Subscribe) : void; notifyObservers(eventType : string) : void; } interface SubscribesObject{ [key : string] : Array<Subscribe> } class ConcretePublish implements Publish { private subscribes : SubscribesObject; constructor() { this.subscribes = {}; } registerObserver(eventType : string, subscribe : Subscribe) : void { if (!this.subscribes[eventType]) { this.subscribes[eventType] = []; } this.subscribes[eventType].push(subscribe); } remove(eventType : string, subscribe ?: Subscribe) : void { const subscribeArray = this.subscribes[eventType]; if (subscribeArray) { if (!subscribe) { delete this.subscribes[eventType]; } else { for (let i = 0; i < subscribeArray.length; i++) { if (subscribe === subscribeArray[i]) { subscribeArray.splice(i, 1); } } } } } notifyObservers(eventType : string, ...args : any[]) : void { const subscribes = this.subscribes[eventType]; if (subscribes) { subscribes.forEach(subscribe => subscribe.update(...args)) } } } interface Subscribe { update(...value : any[]) : void; } class ConcreteSubscribe1 implements Subscribe { public update(...value : any[]) : void { console.log('已經執行更新操做1,值爲', ...value); } } class ConcreteSubscribe2 implements Subscribe { public update(...value : any[]) : void { console.log('已經執行更新操做2,值爲', ...value); } } function main() { const publish = new ConcretePublish(); const subscribe1 = new ConcreteSubscribe1(); const subscribe2 = new ConcreteSubscribe2(); publish.registerObserver('1', subscribe1); publish.registerObserver('2', subscribe2); publish.notifyObservers('2', '22222'); } main();
相關章節
圖解JavaScript——代碼實現【1】
圖解JavaScript————基礎篇
圖解JavaScript————進階篇
圖解23種設計模式(TypeScript版)參考連接
Prmose/A+
Promise源碼實現
ES6入門教程es6歡迎你們關注公衆號(回覆「異步」獲取本節的思惟導圖,回覆「書籍」獲取大量前端學習資料,回覆「前端視頻」獲取大量前端教學視頻)
面試