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