摘要:本篇文章將會嘗試用簡單易懂的語言描述的原理,并且用手擼一個簡單的。一個后可以通過方法,指定和時的回調函數(shù)。實現(xiàn)實現(xiàn)狀態(tài)機因為是一個構造函數(shù),使用的寫法,首先想到的就是有顯式聲明的。
說到Promise,都知道它是比回調函數(shù)更優(yōu)的一種異步編程解決方案,它可以使得異步操作邏輯變得更加清晰,是解決地獄回調的一種嘗試。本篇文章將會嘗試用簡單易懂的語言描述Promise的原理,并且用es6手擼一個簡單的Promise。1. Promise的原理
原理:promise對象有三種狀態(tài),pending、fulfilled和rejected。promise對象內部保存一個需要執(zhí)行一段時間的異步操作,當異步操作執(zhí)行結束后可以調用resolve或reject方法,來改變promise對象的狀態(tài),狀態(tài)一旦改變就不能再變。new一個promise后可以通過then方法,指定resolved和rejected時的回調函數(shù)。下面是我們日常使用Promise的代碼邏輯。
let getAsyncData = new Promise((resolve, reject) => { // 執(zhí)行一些異步操作 if (// 如果成功) { // ...執(zhí)行代碼 resolve(); } else { // 如果失敗 // ...執(zhí)行代碼 reject(); } }) ); getAsyncData.then(success, fail).then(success, fail)
結合Promise A+規(guī)范,我們就可以分析一下我們要實現(xiàn)一個什么東西:
實現(xiàn)一個狀態(tài)機,有三個狀態(tài),pending、fulfilled、rejected,狀態(tài)之間的轉化只能是pending->fulfilled、pending->rejected,狀態(tài)變化不可逆。
實現(xiàn)一個then方法,可以用來設置成功和失敗的回調
then方法要能被調用多次,所以then方法需要每次返回一個新的promise對象,這樣才能支持鏈式調用。
構造函數(shù)內部要有一個value值,用來保存上次執(zhí)行的結果值,如果報錯,則保存的是異常信息。
那我們現(xiàn)在就按照上面提到的原理和規(guī)范來實現(xiàn)這個Promise構造函數(shù)。
2. 實現(xiàn) 2.1 實現(xiàn)狀態(tài)機// promise.js class Promise { constructor (executor) { this.status = PENDING; this.value = ""; executor(this.resolve, this.reject); } resolve (value) { if (this.status === PENDING) { this.value = value; this.status = FULFILLED; } } reject (value) { if (this.status === PENDING) { this.value = value; this.status = REJECTED; } } then (onfulfilled, onrejected) { if (this.status === FULFILLED) { onfulfilled(this.value); } if (this.status === REJECTED) { onrejected(this.value); } } } const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; const test = new Promise((resolve, reject) => { resolve(100); }); test.then((data) => { console.log(data); },(data) => {});
因為Promise是一個構造函數(shù),使用ES6的寫法,首先想到的就是有顯式constructor聲明的class。上面就是我們用class的實現(xiàn),可以看到這樣我們就實現(xiàn)了這個狀態(tài)機,有status, value兩個屬性和resolve, reject, then三個函數(shù);同時它有pending, fulfilled和rejected三個狀態(tài),其中pending就可以切換為fulfilled或者rejected兩種。
看來起還行的樣子,嘗試著運行一下,報錯了。
ReferenceError: resolve is not defined
這是因為在class中使用this要格外小心,類的方法內部如果含有this,它默認指向類的實例,而如果多帶帶使用這個方法(上面代碼中的resolve(100)),this就會指向該方法運行時所在的環(huán)境,從而因為找不到這個方法而報錯。所以,要在構造函數(shù)中綁定this。constructor改為
constructor (executor) { this.status = PENDING; this.value = ""; this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); this.then = this.then.bind(this); executor(this.resolve, this.reject); }
再運行一下,輸出了100,但是現(xiàn)在其實不是一個異步處理方案,代碼先運行了resolve(100)然后又運行了then函數(shù),其實對于異步的情況沒有處理,不信的話就給resolve加一個setTimeout,好了,代碼又沒有輸出了。
2.2 實現(xiàn)異步設置狀態(tài)作為一個異步處理的函數(shù),在使用的時候,我們肯定是會先設置好不同異步返回后的處理邏輯(即then的成功、失敗調用函數(shù)),然后安心等待異步執(zhí)行,最后再異步結束后,系統(tǒng)會自動根據我們的邏輯選擇調用不同回調函數(shù)。換句話說,then函數(shù)要對status為pending的狀態(tài)進行處理。處理的原理是設置兩個數(shù)組,在pending狀態(tài)下分別保存成功和失敗回調函數(shù),當狀態(tài)改變后,再根據狀態(tài)去調用數(shù)組中保存的回調函數(shù)。
我們將代碼改變如下:
class Promise { constructor (executor) { this.status = PENDING; this.value = ""; this.onfulfilledArr = []; this.onrejectedArr = []; this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); this.then = this.then.bind(this); executor(this.resolve, this.reject); } resolve (value) { if (this.status === PENDING) { this.value = value; this.onfulfilledArr.forEach(item => { item(this.value); }) this.status = FULFILLED; } } reject (value) { if (this.status === PENDING) { this.value = value; this.onrejectedArr.forEach(item => { item(this.value); }) this.status = REJECTED; } } then (onfulfilled, onrejected) { if (this.status === FULFILLED) { onfulfilled(this.value); } if (this.status === REJECTED) { onrejected(this.value); } if (this.status === PENDING) { this.onfulfilledArr.push(onfulfilled); this.onrejectedArr.push(onrejected); } } } const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; const test = new Promise((resolve, reject) => { setTimeout(() => { resolve(100); }, 2000) }); test.then((data) => { console.log(data); },(data) => {});
再運行一下,ok正常輸出了。但是Promise的一大特點就是可以鏈式調用,即test.then(success, fail).then(success, fail)...這就需要then返回一個新的Promise對象,而我們的程序現(xiàn)在明顯的是不支持的。那么繼續(xù)改一下。
2.3 實現(xiàn)鏈式調用再觀察一下鏈式調用,如果成功和失敗的函數(shù)中有返回值,這個值要作為參數(shù)傳給下個then函數(shù)的成功或失敗回調。所以我們要在返回的new Promise中調用相應的函數(shù)。
class Promise { constructor (executor) { this.status = PENDING; this.value = ""; this.onfulfilledArr = []; this.onrejectedArr = []; this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); this.then = this.then.bind(this); executor(this.resolve, this.reject); } resolve (value) { if (this.status === PENDING) { this.value = value; this.onfulfilledArr.forEach(item => { item(this.value); }) this.status = FULFILLED; } } reject (value) { if (this.status === PENDING) { this.value = value; this.onrejectedArr.forEach(item => { item(this.value); }) this.status = REJECTED; } } then (onfulfilled, onrejected) { if (this.status === FULFILLED) { const res = onfulfilled(this.value); return new Promise(function(resolve, reject) { resolve(res); }) } if (this.status === REJECTED) { const res = onrejected(this.value); return new Promise(function(resolve, reject) { reject(res); }) } if (this.status === PENDING) { const self = this; return new Promise(function(resolve, reject) { self.onfulfilledArr.push(() => { const res = onfulfilled(self.value) resolve(res); }); self.onrejectedArr.push(() => { const res = onrejected(self.value) reject(res); }); }) } } } const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; const test = new Promise((resolve, reject) => { setTimeout(() => { resolve(100); }, 2000) }); test.then((data) => { console.log(data); return data + 5; },(data) => {}) .then((data) => { console.log(data) },(data) => {});
再運行一下,輸出100,105。好了,一個簡單的Promise就實現(xiàn)好了。
3. 總結Promise其實就是對異步操作的一種封裝方式,可以使得回調的流程變得清晰一些,但是本質上并不解決回調地獄。因為如果有多個異步操作嵌套,then也要一直寫下去。所以后來ES6又有了Generator,允許我們用同步的方式來寫異步的代碼,以及它的語法糖async/await,當然這就是后話了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/96847.html
摘要:介紹簡單點來說就就是一個配置文件,所有的魔力都是在這一個文件中發(fā)生的。一安裝全局安裝在文件夾里面也需要安裝這個是在你根目錄下進行的全局安裝記得加太慢,推薦用的鏡像安裝方法一樣。二創(chuàng)建項目新建文件夾命令行輸入命令。 介紹:webpack簡單點來說就就是一個配置文件,所有的魔力都是在這一個文件中發(fā)生的。 這個配置文件主要分為三大塊 entry 入口文件 讓webpack用哪個文件作為項目的...
摘要:更好的異步編程上面的方法可以適用于那些比較簡單的異步工作流程。小結的組合目前是最強大,也是最優(yōu)雅的異步流程管理編程方式。 訪問原文地址 generators主要作用就是提供了一種,單線程的,很像同步方法的編程風格,方便你把異步實現(xiàn)的那些細節(jié)藏在別處。這讓我們可以用一種很自然的方式書寫我們代碼中的流程和狀態(tài)邏輯,不再需要去遵循那些奇怪的異步編程風格。 換句話說,通過將我們generato...
摘要:手寫一款符合規(guī)范的長篇預警有點長,可以選擇性觀看。初始狀態(tài)是,狀態(tài)可以有或者不能從轉換為或者從轉換成即只要由狀態(tài)轉換為其他狀態(tài)后,狀態(tài)就不可變更。 手寫一款符合Promise/A+規(guī)范的Promise 長篇預警!有點長,可以選擇性觀看。如果對Promise源碼不是很清楚,還是推薦從頭看,相信你認真從頭看到尾,并且去實際操作了,肯定會有收獲的。主要是代碼部分有點多,不過好多都是重復的,不...
摘要:圖片壓縮的原理大同小異,這里直接引用官方文檔的原話基本原理是通過渲染圖片,再通過方法壓縮保存為字符串能夠編譯為格式的圖片。這個過程我自己手擼過,代碼很多,更不用提有各種的兼容性坑,所以最后權衡再三還是直接換成了這個插件。 慣例,先貼傳送門:https://github.com/think2011/localResizeIMG 首先說到,為嘛要壓縮圖片,這需求一般出現(xiàn)在需要上傳照片(尤其...
閱讀 3868·2021-07-28 18:10
閱讀 2577·2019-08-30 15:44
閱讀 1083·2019-08-30 14:07
閱讀 3455·2019-08-29 17:20
閱讀 1577·2019-08-26 18:35
閱讀 3533·2019-08-26 13:42
閱讀 1816·2019-08-26 11:58
閱讀 1585·2019-08-23 18:33