<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          圖解JavaScript——代碼實(shí)現(xiàn)【2】(重點(diǎn)是Promise、Async、發(fā)布/訂閱原理實(shí)現(xiàn))

          共 13775字,需瀏覽 28分鐘

           ·

          2020-10-12 17:16

          本節(jié)主要闡述六種異步方案:回調(diào)函數(shù)、事件監(jiān)聽、發(fā)布/訂閱、Promise、Generator和Async。其中重點(diǎn)是發(fā)布/訂閱、Promise、Async的原理實(shí)現(xiàn),通過對這幾點(diǎn)的了解,希望我們前端切圖仔能夠在修煉內(nèi)功的路上更進(jìn)一步。

          一、六種異步方案

          1.1 回調(diào)函數(shù)

          異步編程的最基本方法,把任務(wù)的第二段單獨(dú)寫在一個函數(shù)里面,等到重新執(zhí)行這個任務(wù)的時候,就直接調(diào)用這個函數(shù)。

          • 優(yōu)點(diǎn):簡單、容易理解和實(shí)現(xiàn)。

          • 缺點(diǎn):多次調(diào)用會使代碼結(jié)構(gòu)混亂,形成回調(diào)地獄。

          function sleep(time, callback) {
          setTimeout(() => {
          // 一些邏輯代碼
          callback();
          }, time);
          }

          1.2 事件監(jiān)聽

          異步任務(wù)的執(zhí)行不取決于代碼的執(zhí)行順序,而取決于某個事件是否發(fā)生。

          • 優(yōu)點(diǎn):易于理解,此外對于每個事件可以指定多個回調(diào)函數(shù),而且可以“去耦合”,有利于實(shí)現(xiàn)模塊化。

          • 缺點(diǎn):整個程序都要變成事件驅(qū)動型,運(yùn)行流程會變得很不清晰。

          dom.addEventListener('click', () => {
          console.log('dom被點(diǎn)擊后觸發(fā)!!!');
          })

          1.3 發(fā)布/訂閱

          發(fā)布/訂閱模式在觀察者模式的基礎(chǔ)上,在目標(biāo)和觀察者之間增加一個調(diào)度中心。訂閱者(觀察者)把自己想要訂閱的事件注冊到調(diào)度中心,當(dāng)該事件觸發(fā)的時候,發(fā)布者(目標(biāo))發(fā)布該事件到調(diào)度中心,由調(diào)度中心統(tǒng)一調(diào)度訂閱者注冊到調(diào)度中心的處理代碼。

          1.4 Promise

          Promise 是異步編程的一種解決方案,是為解決回調(diào)函數(shù)地獄這個問題而提出的,它不是新的語法功能,而是一種新的寫法,允許將回調(diào)函數(shù)的嵌套改為鏈?zhǔn)秸{(diào)用。

          • 優(yōu)點(diǎn):將回調(diào)函數(shù)的嵌套改為了鏈?zhǔn)秸{(diào)用;使用then方法以后,異步任務(wù)的兩端執(zhí)行看的更加清楚。

          • 缺點(diǎn):Promise 的最大問題是代碼冗余,原來的任務(wù)被 Promise 包裝了一下,不管什么操作,一眼看去都是一堆then,原來的語義變得很不清楚。

          const promise = new Promise((resolve, reject) => {
          if (/*如果異步成功*/) {
          resolve(value);
          } else {
          reject(error);
          }
          });

          promise.then((value) => {
          // ...success
          }, (reason) => {
          // ...failure
          })

          1.5 Generator

          Generator 函數(shù)是ES6提供的一種異步編程解決方案,語法行為與傳統(tǒng)函數(shù)完全不同。其最大特點(diǎn)是可以控制函數(shù)的執(zhí)行。

          • 優(yōu)點(diǎn):異步操作表示的很簡潔,此外可以控制函數(shù)的執(zhí)行。

          • 缺點(diǎn):流程管理不方便,不能實(shí)現(xiàn)自動化的流程管理。

          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}

          1.6 Async

          ES2017 標(biāo)準(zhǔn)引入了async函數(shù),使得異步操作變得更加方便。簡言之,該函數(shù)就是Generator函數(shù)的語法糖。

          • 優(yōu)點(diǎn):內(nèi)置執(zhí)行器,可以自動執(zhí)行;語義相比Generator更加清晰;返回值是Promise,比Generator函數(shù)的返回值是Iterator對象操作更加方便。

          • 增加學(xué)習(xí)成本。

          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原理實(shí)現(xiàn)

          不管是實(shí)際開發(fā)中還是面試過程中,各位老鐵們對Promise肯定不會陌生,下面就讓我們一起來嘮一嘮Promsie的實(shí)現(xiàn)原理,根據(jù)PromiseA+規(guī)范來進(jìn)行實(shí)現(xiàn),然后對其相關(guān)的靜態(tài)方法(Promise.resolve()、Promise.reject()、Promise.all()、Promise.race())和實(shí)例方法(Promise.prototype.catch()、Promise.prototype.finally())進(jìn)行實(shí)現(xiàn)。

          2.1 思考一下

          首先用一幅圖來展示一下我考慮實(shí)現(xiàn)這個函數(shù)的思路吧。

          2.2 根據(jù)Promise/A+規(guī)范實(shí)現(xiàn)Promise

          人家有相關(guān)標(biāo)準(zhǔn),我們就要遵守,畢竟遵紀(jì)守法才是好公民,現(xiàn)在只能硬著頭皮把這個標(biāo)準(zhǔn)過一遍。

          下面就是基于Promise/A+規(guī)范實(shí)現(xiàn)的代碼,已經(jīng)經(jīng)過promises-aplus-tests庫進(jìn)行了驗(yàn)證。

          const PENDING = 'pending';
          const FULFILLED = 'fulfilled';
          const REJECTED = 'rejected';
          /**
          * Promise構(gòu)造函數(shù)
          * excutor: 內(nèi)部同步執(zhí)行的函數(shù)
          */
          class Promise {
          constructor(excutor) {
          const self = this;
          self.status = PENDING;
          self.onFulfilled = [];// 成功的回調(diào)
          self.onRejected = [];// 失敗的回調(diào)

          // 異步處理成功調(diào)用的函數(shù)
          // PromiseA+ 2.1 狀態(tài)只能由Pending轉(zhuǎn)為fulfilled或rejected;fulfilled狀態(tài)必須有一個value值;rejected狀態(tài)必須有一個reason值。
          function resolve(value) {
          if (self.status === PENDING) {
          self.status = FULFILLED;
          self.value = value;
          // PromiseA+ 2.2.6.1 相同promise的then可以被調(diào)用多次,當(dāng)promise變?yōu)閒ulfilled狀態(tài),全部的onFulfilled回調(diào)按照原始調(diào)用then的順序執(zhí)行
          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可以被調(diào)用多次,當(dāng)promise變?yōu)閞ejected狀態(tài),全部的onRejected回調(diào)按照原始調(diào)用then的順序執(zhí)行
          self.onRejected.forEach(fn => fn());
          }
          }

          try {
          excutor(resolve, reject);
          } catch (e) {
          reject(e);
          }
          }

          then(onFulfilled, onRejected) {
          // PromiseA+ 2.2.1 onFulfilled和onRejected是可選參數(shù)
          // PromiseA+ 2.2.5 onFulfilled和onRejected必須被作為函數(shù)調(diào)用
          // PromiseA+ 2.2.7.3 如果onFulfilled不是函數(shù)且promise1狀態(tài)是fulfilled,則promise2有相同的值且也是fulfilled狀態(tài)
          // PromiseA+ 2.2.7.4 如果onRejected不是函數(shù)且promise1狀態(tài)是rejected,則promise2有相同的值且也是rejected狀態(tài)
          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需要在自己的執(zhí)行上下文棧里被調(diào)用,所以此處用setTimeout
          setTimeout(() => {
          try {
          // PromiseA+ 2.2.2 如果onFulfilled是函數(shù),則在fulfilled狀態(tài)之后調(diào)用,第一個參數(shù)為value
          // PromiseA+ 2.2.3 如果onRejected是函數(shù),則在rejected狀態(tài)之后調(diào)用,第一個參數(shù)為reason
          const x = callback(data);
          // PromiseA+ 2.2.7.1 如果onFulfilled或onRejected返回一個x值,運(yùn)行這[[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都被調(diào)用,或者對同一個參數(shù)進(jìn)行多次調(diào)用,那么第一次調(diào)用優(yōu)先,以后的調(diào)用都會被忽略。
          let used;
          try {
          // PromiseA+ 2.3.3.1 let then be x.then
          // PromiseA+ 2.3.2 調(diào)用then方法已經(jīng)包含了該條(該條是x是promise的處理)。
          let then = x.then;

          if (typeof then === 'function') {
          // PromiseA+ 2.3.3.3如果then是一個函數(shù),用x作為this調(diào)用它。第一個參數(shù)是resolvePromise,第二個參數(shù)是rejectPromise
          // PromiseA+ 2.3.3.3.1 如果resolvePromise用一個值y調(diào)用,運(yùn)行[[Resolve]](promise, y)
          // PromiseA+ 2.3.3.3.2 如果rejectPromise用一個原因r調(diào)用,用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不是一個函數(shù),變?yōu)閒ulfilled狀態(tài)并傳值為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 如果調(diào)用then拋出異常,但是resolvePromise或rejectPromise已經(jīng)執(zhí)行,則忽略它
          if (used) return;
          used = true;
          reject(e);
          }

          } else {
          // PromiseA+ 2.3.4 如果x不是一個對象或函數(shù),狀態(tài)變?yōu)閒ulfilled并傳值x
          resolve(x);
          }
          }

          2.2 其他方法

          按照Promise/A+規(guī)范實(shí)現(xiàn)了Promise的核心內(nèi)容,但是其只實(shí)現(xiàn)了Promise.prototype.then()方法,那其它方法呢?下面我們就嘮一嘮其它方法,包括靜態(tài)方法(Promise.resolve()、Promise.reject()、Promise.all()、Promise.race())和實(shí)例方法(Promise.prototype.catch()、Promise.prototype.finally())。

          2.2.1 Promise.resolve()

          class Promise {
          // ...
          // 將現(xiàn)有對象轉(zhuǎn)為 Promise 對象
          static resolve(value) {
          // 如果參數(shù)是 Promise 實(shí)例,那么Promise.resolve將不做任何修改、原封不動地返回這個實(shí)例。
          if (value instanceof Promise) return value;

          // 參數(shù)是一個thenable對象(具有then方法的對象),Promise.resolve方法會將這個對象轉(zhuǎn)為 Promise 對象,然后就立即執(zhí)行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);
          })
          }
          }

          // 參數(shù)不是具有then方法的對象,或根本就不是對象,Promise.resolve方法返回一個新的 Promise 對象,狀態(tài)為resolved。
          return new Promise((resolve, reject) => {
          resolve(value);
          })
          }
          }

          2.2.2 Promise.reject()

          class Promise {
          // ...
          // 返回一個新的 Promise 實(shí)例,該實(shí)例的狀態(tài)為rejected。
          static reject(reason) {
          return new Promise((resolve, reject) => {
          reject(reason);
          })
          }
          }

          2.2.3 Promise.all()

          class Promise {
          // ...
          // 用于將多個 Promise 實(shí)例,包裝成一個新的 Promise 實(shí)例。只有所有狀態(tài)都變?yōu)閒ulfilled,p的狀態(tài)才會是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);
          })
          })
          })
          }
          }

          2.2.4 Promise.race()

          class Promise {
          // ...
          // 只要有一個實(shí)例率先改變狀態(tài),狀態(tài)就跟著改變。那個率先改變的 Promise 實(shí)例的返回值,就傳遞給回調(diào)函數(shù)。
          static race(promises) {
          return new Promise((resolve, reject) => {
          promises.forEach((p, index) => {
          Promise.resolve(p).then(value => {
          resolve(value);
          }, reason => {
          reject(reason);
          })
          })
          })
          }
          }

          2.2.5 Promise.catch()

          class Promise {
          // ...
          // 是.then(null, rejection)或.then(undefined, rejection)的別名,用于指定發(fā)生錯誤時的回調(diào)函數(shù)。
          catch(onRejected) {
          return this.then(undefined, onRejected);
          }
          }

          2.2.6 Promise.finally()

          class Promise {
          // ...
          // 用于指定不管 Promise 對象最后狀態(tài)如何,都會執(zhí)行的操作。
          finally(callback) {
          return this.then(
          value => Promise.resolve(callback()).then(() => value),
          reason => Promise.resolve(callback()).then(() => { throw reason })
          )
          }
          }

          三、Async原理實(shí)現(xiàn)

          在開發(fā)過程中常用的另一種異步方案莫過于Async,通過async函數(shù)的引入使得異步操作變得更加方便。實(shí)質(zhì)上,async是Generator的語法糖,最大的亮點(diǎn)是async內(nèi)置執(zhí)行器,調(diào)用后即可自動執(zhí)行,不像Generator需要調(diào)用next()方法才能執(zhí)行。

          這是Async的實(shí)現(xiàn)原理,即將Generator函數(shù)作為參數(shù)放入run函數(shù)中,最終實(shí)現(xiàn)自動執(zhí)行并返回Promise對象。

          function run(genF) {
          // 返回值是Promise
          return new Promise((resolve, reject) => {
          const gen = genF();
          function step(nextF) {
          let next;
          try {
          // 執(zhí)行該函數(shù),獲取一個有著value和done兩個屬性的對象
          next = nextF();
          } catch (e) {
          // 出現(xiàn)異常則將該P(yáng)romise變?yōu)閞ejected狀態(tài)
          reject(e);
          }

          // 判斷是否到達(dá)末尾,Generator函數(shù)到達(dá)末尾則將該P(yáng)romise變?yōu)閒ulfilled狀態(tài)
          if (next.done) {
          return resolve(next.value);
          }

          // 沒到達(dá)末尾,則利用Promise封裝該value,直到執(zhí)行完畢,反復(fù)調(diào)用step函數(shù),實(shí)現(xiàn)自動執(zhí)行
          Promise.resolve(next.value).then((v) => {
          step(() => gen.next(v))
          }, (e) => {
          step(() => gen.throw(e))
          })
          }

          step(() => gen.next(undefined));
          })
          }

          四、發(fā)布/訂閱實(shí)現(xiàn)

          更加詳細(xì)內(nèi)容可以參考《圖解23種設(shè)計(jì)模式》

          發(fā)布/訂閱模式在觀察者模式的基礎(chǔ)上,在目標(biāo)和觀察者之間增加一個調(diào)度中心。訂閱者(觀察者)把自己想要訂閱的事件注冊到調(diào)度中心,當(dāng)該事件觸發(fā)的時候,發(fā)布者(目標(biāo))發(fā)布該事件到調(diào)度中心,由調(diào)度中心統(tǒng)一調(diào)度訂閱者注冊到調(diào)度中心的處理代碼。

          // 發(fā)布訂閱(TypeScript版)
          interface Publish {
          registerObserver(eventType : string, subscribe : Subscribe) : void;
          remove(eventType : string, subscribe ?: Subscribe) : void;
          notifyObservers(eventType : string) : void;
          }
          interface SubscribesObject{
          [key : string] : Array
          }
          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('已經(jīng)執(zhí)行更新操作1,值為', ...value);
          }
          }
          class ConcreteSubscribe2 implements Subscribe {
          public update(...value : any[]) : void {
          console.log('已經(jīng)執(zhí)行更新操作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();

          相關(guān)章節(jié)
          圖解JavaScript——代碼實(shí)現(xiàn)【1】
          圖解JavaScript————基礎(chǔ)篇
          圖解JavaScript————進(jìn)階篇
          圖解23種設(shè)計(jì)模式(TypeScript版)

          參考鏈接
          Prmose/A+
          Promise源碼實(shí)現(xiàn)
          ES6入門教程

          瀏覽 38
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  蜜桃av免费 | 人人超碰人人 | 波多一区 | A级性爱视频 | 蜜芽欧洲无码精品 |