摘要:嗝首先,我們通過字面可以看出來是一種解決方案,而且還有兩種傳統的解決方案回調函數和事件,,那么我們就來先聊聊這兩種方案。
前言
雖然今年已經18年,但是今天還是要繼續聊聊ES6的東西,ES6已經過去幾年,可是我們對于ES6的語法究竟是掌握了什么程度,是了解?會用?還是精通?相信大家和我一樣都對自己有著一個提升的心,對于新玩具可不能僅僅了解,對于其中的思想才是最吸引人的,所以接下來會通過一篇文章,來讓大家對于Promise這個玩具做到精通的程度!!!
打開一瓶冰闊落~~~
PromisePromise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最早提出和實現,ES6將其寫進了語言標準,統一了用法,原生提供了Promise對象。
嗝~
首先,我們通過字面可以看出來Pormise是一種解決方案,而且還有兩種傳統的解決方案·回調函數和事件,ok,那么我們就來先聊聊這兩種方案。
回調函數 Callback回調函數想必大家都不陌生,就是我們常見的把一個函數當做參數傳遞給另外一個函數,在滿足了一定的條件之后再去執行回調,比如我們想要實現一個在三秒后去計算1到5的和,那么:
// 求和函數 function sum () { return eval([...arguments].join("+")) } // 三秒后執行函數 function asycnGetSum (callback) { setTimeout(function(){ var result = callback(1,2,3,4,5); console.log(result) },3000) } asyncGetSum(sum);
這樣的實現就是回調函數,但是如果我要實現在一段動畫,動畫的執行過程是小球先向右移動100px,然后再向下移動100px,在向左移動100px,每段動畫持續時間都是3s.
dom.animate({left:"100px"},3000,"linear",function(){ dom.animate({top:"100px"},3000,"linear",function(){ dom.animate({left:"0px"},3000,"linear",function(){ console.log("動畫 done") }) }) })
這樣就會看到形成了一個回調嵌套,也就是我們常說的回調地獄,導致代碼可讀性十分差。
事件事件處理就是jQuery中的on綁定事件和trigger觸發事件,其實就是我們常見的發布訂閱模式,當我訂閱了一個事件,那么我就是訂閱者,如果發布者發布了數據之后,那么我就要收到相應的通知。
// 定義一個發布中心 let publishCenter = { subscribeArrays:{}, // 定義一個訂閱者回調函數callback subscribe:function(key,callback){ // 增加訂閱者 if(!this.subscribeArrays[key]){ this.subscribeArrays[key] = []; } this.subscribeArrays[key].push(callback) }, publish:function(){ //發布 第一個參數是key let params = [...arguments]; let key = params.shift(); let callbacks = this.subscribeArrays[key]; if(!callbacks || callbacks.length === 0){ // 如果沒人訂閱 那么就返回 return false } for( let i = 0 ; i < callbacks.length; i++ ){ callbacks[i].apply( this, params ); } } }; // 訂閱 一個wantWatermelon事件 publishCenter.subscribe("wantWatermelon",function(){console.log("恰西瓜咯~~")}) //觸發wantWatermelon事件 好咯 可以看到 恰西瓜咯 publishCenter.publish("wantWatermelon")
恰西瓜中~~~
Promise A+嗝~ok,吃完我們進入正題,看到上面異步編程如此如此如此麻煩,對于我這種頭大用戶,當然是拒絕的啊,還好我們有Pormise(Pormise大法好),下面我們就來通過實現一個Promise去更深的了解Promise的原理,首先我們了解一下PromiseA+,它是一種規范,用來約束大家寫的Promise方法的,為了讓大家寫的Promise杜絕一些錯誤,按照我們所期望的流程來走,因此就出現了PromiseA+規范。
Promise特點我們根據PromiseA+文檔來一步一步的看Promise有什么特點。
首先我們看文檔的2.1節,題目是Promise states,也就是說講的是Promise的狀態,那么都說了些什么呢,我們來看一哈:
一個promise只有三種狀態,pending態,fulfilled態(完成態),rejected(拒絕態)
當promise處于pending態時,可能轉化成fulfilled或者rejected
一旦promise的狀態改成了fulfilled后,狀態就不能再改變了,并且需要提供一個不可變的value
一旦promise的狀態改成了rejected后,狀態就不能再改變了,并且需要提供一個不可變的reason
ok,那么我們就開始寫我們自己的Promise,我們先看看一段正常Promise的寫法
// 成功或者失敗是需要提供一個value或者reason let promise1 = new Promise((resolve,rejected)=>{ // 可以發現 當我們new Promise的時候這句話是同步執行的 也就是說當我們初始化一個promise的時候 內部的回調函數(通常我們叫做執行器executor)會立即執行 console.log("hahahha"); // promise內部支持異步 setTimeout(function(){ resolve(123); },100) // throw new Error("error") 我們也可以在執行器內部直接拋出一個錯誤 這時promise會直接變成rejected態 })
根據我們上面的代碼還有PromiseA+規范中的狀態說明,我們可以知道Promise已經有了下面幾個特點
promise有三種狀態 默認pending態 pending可以變成fulfilled(成功態)或者rejected(失敗態),而一旦轉變之后就不能在變成其他值了
promise內部有一個value 用來存儲成功態的結果
promise內部有一個reason 用來存儲失敗態的原因
promise接受一個executor函數,這個函數有兩個參數,一個是resolve方法,一個是reject方法,當執行resolve時,promise狀態改變為fulfilled,執行reject時,promise狀態改變為rejected
默認 new Promise 執行的時候內部的executor函數執行
promise內部支持異步改變狀態
promise內部支持拋出異常,那么該promise的狀態直接改成rejected
我們接下來繼續看PromiseA+文檔:
promise必須要有一個then方法,用來訪問它當前的value或者是reason
該方法接受兩個參數onFulfilled(成功回掉函數),onRejected(失敗回調函數) promise.then(onFulfilled, onRejected)
這兩個參數都是可選參數,如果發現這兩個參數不是函數類型的話,那么就忽略 比如 promise.then().then(data=>console.log(data),err=>console.log(err)) 就可以形成一個值穿透
onFulfilled必須在promise狀態改成fulfilled之后改成調用,并且呢promise內部的value值是這個函數的參數,而且這個函數不能重復調用
onRejected必須在promise狀態改成rejected之后改成調用,并且呢promise內部的reason值是這個函數的參數,而且這個函數不能重復調用
onFulfilled和onRejected這兩個方法必須要在當前執行棧的上下文執行完畢后再調用,其實就是事件循環中的微任務(setTimeout是宏任務,有一定的差異)
onFulfilled和onRejected這兩個方法必須通過函數調用,也就是說 他們倆不是通過this.onFulfilled()或者this.onRejected()調用,直接onFulfilled()或者onRejected()
then方法可以在一個promise上多次調用,也就是我們常見的鏈式調用
如果當前promise的狀態改成了fulfilled那么就要按照順序依次執行then方法中的onFulfilled回調
如果當前promise的狀態改成了rejected那么就要按照順序依次執行then方法中的onRejected回調
then方法必須返回一個promise(接下來我們會把這個promise稱做promise2),類似于 promise2 = promise1.then(onFulfilled, onRejected);
如果呢onFulfilled()或者onRejected()任一一個返回一個值x,那么就要去執行resolvePromise這個函數中去(這個函數是用來處理返回值x遇到的各種值,然后根據這些值去決定我們剛剛then方法中onFulfilled()或者onRejected()這兩個回調返回的promise2的狀態)
如果我們在then中執行onFulfilled()或者onRejected()方法時產生了異常,那么就將promise2用異常的原因e去reject
如果onFulfilled或者onRejected不是函數,并且promise的狀態已經改成了fulfilled或者rejected,那么就用同樣的value或者reason去更新promise2的狀態(其實這一條和第三條一個道理,也就是值得穿透問題)
好吧,我們總結了這么多規范特點,那么我們就用這些先來練練手
/** * 實現一個PromiseA+ * @description 實現一個簡要的promise * @param {Function} executor 執行器 * @author Leslie */ function Promise(executor){ let self = this; self.status = "pending"; // 存儲promise狀態 pending fulfilled rejected. self.value = undefined; // 存儲成功后的值 self.reason = undefined; // 記錄失敗的原因 self.onfulfilledCallbacks = []; // 異步時候收集成功回調 self.onrejectedCallbacks = []; // 異步時候收集失敗回調 function resolve(value){ if(self.status === "pending"){ self.status = "fulfilled";// resolve的時候改變promise的狀態 self.value = value;//修改成功的值 // 異步執行后 調用resolve 再把存儲的then中的成功回調函數執行一遍 self.onfulfilledCallbacks.forEach(element => { element() }); } } function reject(reason){ if(self.status === "pending"){ self.status = "rejected";// reject的時候改變promise的狀態 self.reason = reason; // 修改失敗的原因 // 異步執行后 調用reject 再把存儲的then中的失敗回調函數執行一遍 self.onrejectedCallbacks.forEach(element => { element() }); } } // 如果執行器中拋出異常 那么就把promise的狀態用這個異常reject掉 try { //執行 執行器 executor(resolve,reject); } catch (error) { reject(error) } } Promise.prototype.then = function(onfulfilled,onrejected){ // onfulfilled then方法中的成功回調 // onrejected then方法中的失敗回調 let self = this; // 如果onfulfilled不是函數 那么就用默認的函數替代 以便達到值穿透 onfulfilled = typeof onfulfilled === "function"?onfulfilled:val=>val; // 如果onrejected不是函數 那么就用默認的函數替代 以便達到值穿透 onrejected = typeof onrejected === "function"?onrejected: err=>{throw err} let promise2 = new Promise((resolve,reject)=>{ if(self.status === "fulfilled"){ // 加入setTimeout 模擬異步 // 如果調用then的時候promise 的狀態已經變成了fulfilled 那么就調用成功回調 并且傳遞參數為 成功的value setTimeout(function(){ // 如果執行回調發生了異常 那么就用這個異常作為promise2的失敗原因 try { // x 是執行成功回調的結果 let x = onfulfilled(self.value); // 調用resolvePromise函數 根據x的值 來決定promise2的狀態 resolvePromise(promise2,x,resolve,reject); } catch (error) { reject(error) } },0) } if(self.status === "rejected"){ // 加入setTimeout 模擬異步 // 如果調用then的時候promise 的狀態已經變成了rejected 那么就調用失敗回調 并且傳遞參數為 失敗的reason setTimeout(function(){ // 如果執行回調發生了異常 那么就用這個異常作為promise2的失敗原因 try { // x 是執行失敗回調的結果 let x = onrejected(self.reason); // 調用resolvePromise函數 根據x的值 來決定promise2的狀態 resolvePromise(promise2,x,resolve,reject); } catch (error) { reject(error) } },0) } if(self.status === "pending"){ //如果調用then的時候promise的狀態還是pending,說明promsie執行器內部的resolve或者reject是異步執行的,那么就需要先把then方法中的成功回調和失敗回調存儲襲來,等待promise的狀態改成fulfilled或者rejected時候再按順序執行相關回調 self.onfulfilledCallbacks.push(()=>{ //setTimeout模擬異步 setTimeout(function(){ // 如果執行回調發生了異常 那么就用這個異常作為promise2的失敗原因 try { // x 是執行成功回調的結果 let x = onfulfilled(self.value) // 調用resolvePromise函數 根據x的值 來決定promise2的狀態 resolvePromise(promise2,x,resolve,reject); } catch (error) { reject(error) } },0) }) self.onrejectedCallbacks.push(()=>{ //setTimeout模擬異步 setTimeout(function(){ // 如果執行回調發生了異常 那么就用這個異常作為promise2的失敗原因 try { // x 是執行失敗回調的結果 let x = onrejected(self.reason) // 調用resolvePromise函數 根據x的值 來決定promise2的狀態 resolvePromise(promise2,x,resolve,reject); } catch (error) { reject(error) } },0) }) } }) return promise2; }
一氣呵成,是不是覺得之前總結出的特點十分有效,對著特點十分順暢的就擼完了代碼~
那么就讓我們接著來看看promiseA+文檔里還有些什么內容吧
resolvePromise這個函數呢會決定promise2用什么樣的狀態,如果x是一個普通值,那么就直接采用x,如果x是一個promise那么就將這個promise的狀態當成是promise2的狀態
判斷如果x和promise2是一個對象,即promise2 === x,那么就陷入了循環調用,這時候promise2就會以一個TypeError為reason轉化為rejected
如果x是一個promise,那么promise2就采用x的狀態,用和x相同的value去resolve,或者用和x相同的reason去reject
如果x是一個對象或者是函數 那么就先執行let then = x.then
如果x不是一個對象或者函數 那么就resolve 這個x
如果在執行上面的語句中報錯了,那么就用這個錯誤原因去reject promise2
如果then是一個函數,那么就執行then.call(x,resolveCallback,rejectCallback)
如果then不是一個函數,那么就resolve這個x
如果x是fulfilled態 那么就會走resolveCallback這個函數,這時候就默認把成功的value作為參數y傳遞給resolveCallback,即y=>resolvePromise(promise2,y),繼續調用resolvePromise這個函數 確保 返回值是一個普通值而不是promise
如果x是rejected態 那么就把這個失敗的原因reason作為promise2的失敗原因reject出去
如果resolveCallback,rejectCallback這兩個函數已經被調用了,或者多次被相同的參數調用,那么就確保只調第一次,剩下的都忽略掉
如果調用then拋出異常了,并且如果resolveCallback,rejectCallback這兩個函數已經被調用了,那么就忽略這個異常,否則就用這個異常作為promise2的reject原因
我們又又又又又又總結了這么多,好吧不說了總結多少就開擼吧。
/** * 用來處理then方法返回結果包裝成promise 方便鏈式調用 * @param {*} promise2 then方法執行產生的promise 方便鏈式調用 * @param {*} x then方法執行完成功回調或者失敗回調后的result * @param {*} resolve 返回的promise的resolve方法 用來更改promise最后的狀態 * @param {*} reject 返回的promise的reject方法 用來更改promise最后的狀態 */ function resolvePromise(promise2,x,resolve,reject){ // 首先判斷x和promise2是否是同一引用 如果是 那么就用一個類型錯誤作為Promise2的失敗原因reject if( promise2 === x) return reject(new TypeError("typeError:大佬,你循環引用了!")); // called 用來記錄promise2的狀態改變,一旦發生改變了 就不允許 再改成其他狀態 let called; if( x !== null && ( typeof x === "object" || typeof x === "function")){ // 如果x是一個對象或者函數 那么他就有可能是promise 需要注意 null typeof也是 object 所以需要排除掉 //先獲得x中的then 如果這一步發生異常了,那么就直接把異常原因reject掉 try { let then = x.then;//防止別人瞎寫報錯 if(typeof then === "function"){ //如果then是個函數 那么就調用then 并且把成功回調和失敗回調傳進去,如果x是一個promise 并且最終狀態時成功,那么就會執行成功的回調,如果失敗就會執行失敗的回調如果失敗了,就把失敗的原因reject出去,做為promise2的失敗原因,如果成功了那么成功的value時y,這個y有可能仍然是promise,所以需要遞歸調用resolvePromise這個方法 直達返回值不是一個promise then.call(x,y => { if(called) return; called = true; resolvePromise(promise2,y,resolve,reject) }, error=>{ if(called) return called = true; reject(error) }) }else{ resolve(x) } } catch (error) { if(called) return called = true; reject(error) } }else{ // 如果是一個普通值 那么就直接把x作為promise2的成功value resolve掉 resolve(x) } }
finnnnnnnnnally,我們終于通過我們的不懈努力實現了一個基于PromiseA+規范的Promise!
最后呢為了完美,我們還要在這個promise上實現Promise.resolve,Promise.reject,以及catch,Promise.all和Promise.race這些方法。
Promise的一些方法Promise.resolve = function(value){ return new Promise((resolve,reject)=>{ resolve(value) }) } Promise.reject = function(reason){ return new Promise((resolve,reject)=>{ reject(reason) }) } Promise.prototype.catch = function(onRejected){ return this.then(null,onRejected) } Promise.all = function(promises){ return new Promise((resolve,reject)=>{ let arr = []; let i = 0; function getResult(index,value){ arr[index] = value; if(++i == promises.length) { resolve(arr) } } for(let i = 0;iPromise 語法糖{ getResult(i,data) },reject) } }) } Promise.race = function(promises){ return new Promise((resolve,reject)=>{ for(let i = 0 ; i < promises.length ; i++){ promises[i].then(resolve,reject) } }) }
恰完西瓜來口糖,語法糖是為了讓我們書寫promise的時候能夠更加的快速,所以做了一層改變,我們來看一個例子,比如當我們封裝一個異步讀取圖片的寬高函數
// 原來的方式 let getImgWidthHeight = function(imgUrl){ return new Promise((resolve,reject)=>{ let img = new Image(); img.onload = function(){ resolve(img.width+"-"+img.height) } img.onerror = function(e){ reject(e) } img.src = imgUrl; }) }
是不是覺得怎么寫起來有點舒服但又有點不舒服,好像我每次都要去寫執行器??!為什么!好的,沒有為什么,既然不舒服 我們就改!
// 實現一個promise的語法糖 Promise.defer = Promise.deferred = function (){ let dfd = {}; dfd.promise = new Promise((resolve,reject)=>{ dfd.resolve = resolve; dfd.reject = reject; }) return dfd }
有了上面的語法糖我們再看一下那個圖片的函數怎么寫
let newGetImgWidthHeight = function(imgUrl){ let dfd = Promise.defer(); let img = new Image(); img.onload = function(){ dfd.resolve(img.width+"-"+img.height) } img.onerror = function(e){ dfd.reject(e) } img.url = imgUrl; return dfd.promise }
是不是發現我們少了一層函數嵌套,呼 得勁
npm install promises-aplus-tests -g
既然我們都說了我們是遵循promiseA+規范的,那至少要拿出點證據來是不是,不然是不是說服不了大家,那么我們就用promises-aplus-tests這個包來檢測我們寫的promise究竟怎么樣呢!安裝完成之后來跑一下我們的promise
最終跑出來我們全部通過測試!酷!晚餐再加個雞腿~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/96324.html
摘要:周五就想寫這篇文章,但是無奈花花世界的誘惑太多就一直拖到了今天,自責遍進入正題對象用于表示一個異步操作的最終狀態完成或失敗,以及其返回的值。 周五就想寫這篇文章,但是無奈花花世界的誘惑太多……就一直拖到了今天,自責1e4遍;進入正題Promise: Promise 對象用于表示一個異步操作的最終狀態(完成或失?。约捌浞祷氐闹怠?上為MDNPromise的定義;ES6規定Promis...
你有遇見過給bind返回的函數做new操作的場景,本篇主要講述的就是實現一下兼容new操作的bind寫法,順便學習一下new操作符,為大家提供下參考?! 〈蠹铱梢匀タ聪玛P于 JS 中 bind 方法的實現的文章,并給出了實現: Function.prototype.myBind=function(thisArg,...prefixArgs){ constfn=this; return...
摘要:傳入的回調函數也不是一個函數類型,那怎么辦規范中說忽略它就好了。因此需要判斷一下回調函數的類型,如果明確是個函數再執行它。 Promise是什么 所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處...
摘要:先說下我面試情況,我一共面試了家公司。篇在我面試的眾多公司里,只有同城的面問到相關問題,其他公司壓根沒問。我自己回答的是自己開發組件面臨的問題。完全不用擔心對方到時候打電話核對的問題。 2019的5月9號,離發工資還有1天的時候,我的領導親切把我叫到辦公室跟我說:阿郭,我們公司要倒閉了,錢是沒有的啦,為了不耽誤你,你趕緊出去找工作吧。聽到這話,我虎軀一震,這已經是第2個月沒工資了。 公...
閱讀 1338·2023-04-25 15:21
閱讀 2670·2021-11-24 10:23
閱讀 3397·2021-10-11 10:59
閱讀 3242·2021-09-03 10:28
閱讀 1731·2019-08-26 13:45
閱讀 2319·2019-08-26 12:11
閱讀 921·2019-08-26 12:00
閱讀 1705·2019-08-26 10:44