圖解JavaScript——代碼實現(六種異步方案,重點是Promise、Async、發佈/訂閱原理實現,真香)

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關注公衆號“"},{"type":"text","marks":[{"type":"strong"}],"text":"執鳶者"},{"type":"text","text":"”,回覆“"},{"type":"text","marks":[{"type":"strong"}],"text":"書籍"},{"type":"text","text":"”獲取大量前端學習資料,回覆“"},{"type":"text","marks":[{"type":"strong"}],"text":"前端視頻"},{"type":"text","text":"”獲取大量前端教學視頻,回覆“"},{"type":"text","marks":[{"type":"strong"}],"text":"異步"},{"type":"text","text":"”獲取本節整體思維導圖。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9a/9a3d2734b53d10fd2fd73b6e994d989d.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本節主要闡述六種異步方案:回調函數、事件監聽、發佈/訂閱、Promise、Generator和Async。其中重點是發佈/訂閱、Promise、Async的原理實現,通過對這幾點的瞭解,希望我們前端切圖仔能夠在修煉內功的路上更進一步。"}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"一、六種異步方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/82/82414298af7c89bdc4359e32e3673bba.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.1 回調函數"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"異步編程的最基本方法,把任務的第二段單獨寫在一個函數裏面,等到重新執行這個任務的時候,就直接調用這個函數。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優點:簡單、容易理解和實現。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"缺點:多次調用會使代碼結構混亂,形成回調地獄。"}]}]}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function sleep(time, callback) {\n setTimeout(() => {\n // 一些邏輯代碼\n callback();\n }, time);\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.2 事件監聽"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"異步任務的執行不取決於代碼的執行順序,而取決於某個事件是否發生。"}]}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優點:易於理解,此外對於每個事件可以指定多個回調函數,而且可以“去耦合”,有利於實現模塊化。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"缺點:整個程序都要變成事件驅動型,運行流程會變得很不清晰。"}]}]}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"dom.addEventListener('click', () => {\n console.log('dom被點擊後觸發!!!');\n})"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3 發佈/訂閱"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"發佈/訂閱模式在觀察者模式的基礎上,在目標和觀察者之間增加一個調度中心。訂閱者(觀察者)把自己想要訂閱的事件註冊到調度中心,當該事件觸發的時候,發佈者(目標)發佈該事件到調度中心,由調度中心統一調度訂閱者註冊到調度中心的處理代碼。"}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.4 Promise"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Promise 是異步編程的一種解決方案,是爲解決回調函數地獄這個問題而提出的,它不是新的語法功能,而是一種新的寫法,允許將回調函數的嵌套改爲鏈式調用。"}]}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優點:將回調函數的嵌套改爲了鏈式調用;使用then方法以後,異步任務的兩端執行看的更加清楚。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"缺點:Promise 的最大問題是代碼冗餘,原來的任務被 Promise 包裝了一下,不管什麼操作,一眼看去都是一堆then,原來的語義變得很不清楚。"}]}]}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"const promise = new Promise((resolve, reject) => {\n if (/*如果異步成功*/) {\n resolve(value);\n } else {\n reject(error);\n }\n});\n\npromise.then((value) => {\n // ...success\n}, (reason) => {\n // ...failure\n})"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.5 Generator"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Generator 函數是ES6提供的一種異步編程解決方案,語法行爲與傳統函數完全不同。其最大特點是可以控制函數的執行。"}]}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優點:異步操作表示的很簡潔,此外可以控制函數的執行。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"缺點:流程管理不方便,不能實現自動化的流程管理。"}]}]}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function* genF() {\n yield 'come on!';\n yield 'Front End Engineer';\n return 'goood';\n}\n\nconst gF = genF();\ngF.next();// {value: \"come on!\", done: false}\ngF.next();// {value: \"Front End Engineer\", done: false}\ngF.next();// {value: \"goood\", done: true}\ngF.next();// {value: undefined, done: true}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.6 Async"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ES2017 標準引入了async函數,使得異步操作變得更加方便。簡言之,該函數就是Generator函數的語法糖。"}]}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優點:內置執行器,可以自動執行;語義相比Generator更加清晰;返回值是Promise,比Generator函數的返回值是Iterator對象操作更加方便。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"增加學習成本。"}]}]}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"async function asyncFun() {\n await func1()\n \n await func2();\n \n return '666';\n}\nfunction func1() {\n return new Promise((resolve, reject) => {\n setTimeout(() => {\n resolve('888')\n }, 100);\n }).then((value) => {\n console.log(value);\n });\n}\n\nfunction func2() {\n return new Promise((resolve, reject) => {\n setTimeout(() => {\n resolve('777')\n });\n }).then((value) => {\n console.log(value);\n });\n}\n\nasyncFun().then((value) => {\n console.log(value);\n});\n// 888\n// 777\n// 666"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二、Promise原理實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">不管是實際開發中還是面試過程中,各位老鐵們對Promise肯定不會陌生,下面就讓我們一起來嘮一嘮Promsie的實現原理,根據PromiseA+規範來進行實現,然後對其相關的靜態方法(Promise.resolve()、Promise.reject()、Promise.all()、Promise.race())和實例方法(Promise.prototype.catch()、Promise.prototype.finally())進行實現。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1b/1be6a0c9fd81bc625b19c76b39626c7f.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.1 思考一下"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先用一幅圖來展示一下我考慮實現這個函數的思路吧。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/eb/ebda7d2b879806ef8e219f85a3589452.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.2 根據Promise/A+規範實現Promise"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"人家有相關標準,我們就要遵守,畢竟遵紀守法纔是好公民,現在只能硬着頭皮把這個標準過一遍。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c8/c8b836dc65c1a8488e48c81bb4b700fd.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面就是基於Promise/A+規範實現的代碼,已經經過promises-aplus-tests庫進行了驗證。"}]}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"const PENDING = 'pending';\nconst FULFILLED = 'fulfilled';\nconst REJECTED = 'rejected';\n/**\n * Promise構造函數\n * excutor: 內部同步執行的函數\n */\nclass Promise {\n constructor(excutor) {\n const self = this;\n self.status = PENDING;\n self.onFulfilled = [];// 成功的回調\n self.onRejected = [];// 失敗的回調\n\n // 異步處理成功調用的函數\n // PromiseA+ 2.1 狀態只能由Pending轉爲fulfilled或rejected;fulfilled狀態必須有一個value值;rejected狀態必須有一個reason值。\n function resolve(value) {\n if (self.status === PENDING) {\n self.status = FULFILLED;\n self.value = value;\n // PromiseA+ 2.2.6.1 相同promise的then可以被調用多次,當promise變爲fulfilled狀態,全部的onFulfilled回調按照原始調用then的順序執行\n self.onFulfilled.forEach(fn => fn());\n }\n }\n\n function reject(reason) {\n if (self.status === PENDING) {\n self.status = REJECTED;\n self.reason = reason;\n // PromiseA+ 2.2.6.2 相同promise的then可以被調用多次,當promise變爲rejected狀態,全部的onRejected回調按照原始調用then的順序執行\n self.onRejected.forEach(fn => fn());\n }\n }\n\n try {\n excutor(resolve, reject);\n } catch (e) {\n reject(e);\n }\n }\n\n then(onFulfilled, onRejected) {\n // PromiseA+ 2.2.1 onFulfilled和onRejected是可選參數\n // PromiseA+ 2.2.5 onFulfilled和onRejected必須被作爲函數調用\n // PromiseA+ 2.2.7.3 如果onFulfilled不是函數且promise1狀態是fulfilled,則promise2有相同的值且也是fulfilled狀態\n // PromiseA+ 2.2.7.4 如果onRejected不是函數且promise1狀態是rejected,則promise2有相同的值且也是rejected狀態\n onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;\n onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };\n\n const self = this;\n const promise = new Promise((resolve, reject) => {\n const handle = (callback, data) => {\n // PromiseA+ 2.2.4 onFulfilled或者onRejected需要在自己的執行上下文棧裏被調用,所以此處用setTimeout\n setTimeout(() => {\n try {\n // PromiseA+ 2.2.2 如果onFulfilled是函數,則在fulfilled狀態之後調用,第一個參數爲value\n // PromiseA+ 2.2.3 如果onRejected是函數,則在rejected狀態之後調用,第一個參數爲reason\n const x = callback(data);\n // PromiseA+ 2.2.7.1 如果onFulfilled或onRejected返回一個x值,運行這[[Resolve]](promise2, x)\n resolvePromise(promise, x, resolve, reject);\n } catch (e) {\n // PromiseA+ 2.2.7.2 onFulfilled或onRejected拋出一個異常e,promise2必須以e的理由失敗\n reject(e);\n }\n })\n }\n if (self.status === PENDING) {\n self.onFulfilled.push(() => {\n handle(onFulfilled, self.value);\n });\n\n self.onRejected.push(() => {\n handle(onRejected, self.reason);\n })\n } else if (self.status === FULFILLED) {\n setTimeout(() => {\n handle(onFulfilled, self.value);\n })\n } else if (self.status === REJECTED) {\n setTimeout(() => {\n handle(onRejected, self.reason);\n })\n }\n })\n\n return promise;\n }\n}\n\nfunction resolvePromise(promise, x, resolve, reject) {\n // PromiseA+ 2.3.1 如果promise和x引用同一對象,會以TypeError錯誤reject promise\n if (promise === x) {\n reject(new TypeError('Chaining Cycle'));\n }\n\n if (x && typeof x === 'object' || typeof x === 'function') {\n // PromiseA+ 2.3.3.3.3 如果resolvePromise和rejectPromise都被調用,或者對同一個參數進行多次調用,那麼第一次調用優先,以後的調用都會被忽略。 \n let used;\n try {\n // PromiseA+ 2.3.3.1 let then be x.then\n // PromiseA+ 2.3.2 調用then方法已經包含了該條(該條是x是promise的處理)。\n let then = x.then;\n\n if (typeof then === 'function') {\n // PromiseA+ 2.3.3.3如果then是一個函數,用x作爲this調用它。第一個參數是resolvePromise,第二個參數是rejectPromise\n // PromiseA+ 2.3.3.3.1 如果resolvePromise用一個值y調用,運行[[Resolve]](promise, y)\n // PromiseA+ 2.3.3.3.2 如果rejectPromise用一個原因r調用,用r拒絕promise。\n then.call(x, (y) => {\n if (used) return;\n used = true;\n resolvePromise(promise, y, resolve, reject)\n }, (r) => {\n if (used) return;\n used = true;\n reject(r);\n })\n } else {\n // PromiseA+ 如果then不是一個函數,變爲fulfilled狀態並傳值爲x\n if (used) return;\n used = true;\n resolve(x);\n }\n } catch (e) {\n // PromiseA+ 2.3.3.2 如果檢索屬性x.then拋出異常e,則以e爲原因拒絕promise\n // PromiseA+ 2.3.3.4 如果調用then拋出異常,但是resolvePromise或rejectPromise已經執行,則忽略它\n if (used) return;\n used = true;\n reject(e);\n }\n\n } else {\n // PromiseA+ 2.3.4 如果x不是一個對象或函數,狀態變爲fulfilled並傳值x\n resolve(x);\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.2 其他方法"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按照Promise/A+規範實現了Promise的核心內容,但是其只實現了Promise.prototype.then()方法,那其它方法呢?下面我們就嘮一嘮其它方法,包括靜態方法(Promise.resolve()、Promise.reject()、Promise.all()、Promise.race())和實例方法(Promise.prototype.catch()、Promise.prototype.finally())。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e2/e23156c0f6bd22d7d2b8ac9b8cc14220.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.1 Promise.resolve()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/36/36c149240b8a3b4580f68fc8d0c21554.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 將現有對象轉爲 Promise 對象\n static resolve(value) {\n // 如果參數是 Promise 實例,那麼Promise.resolve將不做任何修改、原封不動地返回這個實例。\n if (value instanceof Promise) return value;\n\n // 參數是一個thenable對象(具有then方法的對象),Promise.resolve方法會將這個對象轉爲 Promise 對象,然後就立即執行thenable對象的then方法。\n if (typeof value === 'object' || typeof value === 'function') {\n try {\n let then = value.then;\n if (typeof then === 'function') {\n return new Promise(then.bind(value));\n }\n } catch (e) {\n return new Promise((resolve, reject) => {\n reject(e);\n })\n }\n }\n\n // 參數不是具有then方法的對象,或根本就不是對象,Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。\n return new Promise((resolve, reject) => {\n resolve(value);\n })\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.2 Promise.reject()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/71/71936987eeebc38a0230ed0eb43cfbeb.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 返回一個新的 Promise 實例,該實例的狀態爲rejected。\n static reject(reason) {\n return new Promise((resolve, reject) => {\n reject(reason);\n })\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.3 Promise.all()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/35/35241eff0dc14099507b7a72cf1da63c.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 用於將多個 Promise 實例,包裝成一個新的 Promise 實例。只有所有狀態都變爲fulfilled,p的狀態纔會是fulfilled\n static all(promises) {\n const values = [];\n let resolvedCount = 0;\n return new Promise((resolve, reject) => {\n promises.forEach((p, index) => {\n Promise.resolve(p).then(value => {\n resolvedCount++;\n values[index] = value;\n if (resolvedCount === promises.length) {\n resolve(values);\n }\n }, reason => {\n reject(reason);\n })\n })\n })\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.4 Promise.race()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/81/8113cf0fcb751e581a0437e20c4def91.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 只要有一個實例率先改變狀態,狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給回調函數。\n static race(promises) {\n return new Promise((resolve, reject) => {\n promises.forEach((p, index) => {\n Promise.resolve(p).then(value => {\n resolve(value);\n }, reason => {\n reject(reason);\n })\n })\n })\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.5 Promise.catch()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e0/e018e951b2c0a876869b69393b0578cd.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。\n catch(onRejected) {\n return this.then(undefined, onRejected);\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.6 Promise.finally()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5f/5fb8a08a8fb7c32ffa8ceb4d44d1bb30.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 用於指定不管 Promise 對象最後狀態如何,都會執行的操作。\n finally(callback) {\n return this.then(\n value => Promise.resolve(callback()).then(() => value),\n reason => Promise.resolve(callback()).then(() => { throw reason })\n )\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三、Async原理實現"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在開發過程中常用的另一種異步方案莫過於Async,通過async函數的引入使得異步操作變得更加方便。實質上,async是Generator的語法糖,最大的亮點是async內置執行器,調用後即可自動執行,不像Generator需要調用next()方法才能執行。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c4/c42bf4c537f0997097363e06b2acb976.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是Async的實現原理,即將Generator函數作爲參數放入run函數中,最終實現自動執行並返回Promise對象。"}]}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function run(genF) {\n // 返回值是Promise\n return new Promise((resolve, reject) => {\n const gen = genF();\n function step(nextF) {\n let next;\n try {\n // 執行該函數,獲取一個有着value和done兩個屬性的對象\n next = nextF();\n } catch (e) {\n // 出現異常則將該Promise變爲rejected狀態\n reject(e);\n }\n\n // 判斷是否到達末尾,Generator函數到達末尾則將該Promise變爲fulfilled狀態\n if (next.done) {\n return resolve(next.value);\n }\n\n // 沒到達末尾,則利用Promise封裝該value,直到執行完畢,反覆調用step函數,實現自動執行\n Promise.resolve(next.value).then((v) => {\n step(() => gen.next(v))\n }, (e) => {\n step(() => gen.throw(e))\n })\n }\n\n step(() => gen.next(undefined));\n })\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"四、發佈/訂閱實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"更加詳細內容可以參考"},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/GzPqSgna9Fwqal-5Oyg5EA","title":""},"content":[{"type":"text","text":"《圖解23種設計模式》"}]}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"發佈/訂閱模式在觀察者模式的基礎上,在目標和觀察者之間增加一個調度中心。訂閱者(觀察者)把自己想要訂閱的事件註冊到調度中心,當該事件觸發的時候,發佈者(目標)發佈該事件到調度中心,由調度中心統一調度訂閱者註冊到調度中心的處理代碼。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4e/4e30a5b625d4623afa639f8f85fa247c.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 發佈訂閱(TypeScript版)\ninterface Publish {\n registerObserver(eventType : string, subscribe : Subscribe) : void;\n remove(eventType : string, subscribe ?: Subscribe) : void;\n notifyObservers(eventType : string) : void;\n}\ninterface SubscribesObject{\n [key : string] : Array\n}\nclass ConcretePublish implements Publish {\n private subscribes : SubscribesObject;\n\n constructor() {\n this.subscribes = {};\n }\n\n registerObserver(eventType : string, subscribe : Subscribe) : void {\n if (!this.subscribes[eventType]) {\n this.subscribes[eventType] = [];\n }\n\n this.subscribes[eventType].push(subscribe);\n }\n\n remove(eventType : string, subscribe ?: Subscribe) : void {\n const subscribeArray = this.subscribes[eventType];\n if (subscribeArray) {\n if (!subscribe) {\n delete this.subscribes[eventType];\n } else {\n for (let i = 0; i < subscribeArray.length; i++) {\n if (subscribe === subscribeArray[i]) {\n subscribeArray.splice(i, 1);\n }\n }\n }\n }\n }\n\n notifyObservers(eventType : string, ...args : any[]) : void {\n const subscribes = this.subscribes[eventType];\n if (subscribes) {\n subscribes.forEach(subscribe => subscribe.update(...args))\n }\n }\n}\n\ninterface Subscribe {\n update(...value : any[]) : void;\n}\n\nclass ConcreteSubscribe1 implements Subscribe {\n public update(...value : any[]) : void {\n console.log('已經執行更新操作1,值爲', ...value);\n }\n}\nclass ConcreteSubscribe2 implements Subscribe {\n public update(...value : any[]) : void {\n console.log('已經執行更新操作2,值爲', ...value);\n }\n}\n\nfunction main() {\n const publish = new ConcretePublish();\n const subscribe1 = new ConcreteSubscribe1();\n const subscribe2 = new ConcreteSubscribe2();\n\n publish.registerObserver('1', subscribe1);\n publish.registerObserver('2', subscribe2);\n\n publish.notifyObservers('2', '22222');\n}\n\nmain();\n"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相關章節
"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/N_QKDJqMX82cjvI1MYnwXQ","title":""},"content":[{"type":"text","text":"圖解JavaScript——代碼實現【1】"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/jOjUPR-t-wX0TI9rD3P45Q","title":""},"content":[{"type":"text","text":"圖解JavaScript————基礎篇"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/sAKkyR9XNtQIYWL6F0PDIA","title":""},"content":[{"type":"text","text":"圖解JavaScript————進階篇"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/GzPqSgna9Fwqal-5Oyg5EA","title":""},"content":[{"type":"text","text":"圖解23種設計模式(TypeScript版)"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"參考鏈接"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://promisesaplus.com/","title":""},"content":[{"type":"text","text":"Prmose/A+"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://segmentfault.com/a/1190000018428848?utm_source=tag-newest","title":""},"content":[{"type":"text","text":"Promise源碼實現"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://es6.ruanyifeng.com/","title":""},"content":[{"type":"text","text":"ES6入門教程"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歡迎大家關注公衆號(回覆“異步”獲取本節的思維導圖,回覆“書籍”獲取大量前端學習資料,回覆“前端視頻”獲取大量前端教學視頻)"}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9a/9a3d2734b53d10fd2fd73b6e994d989d.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章