摘要:意味著代指的操作由于某些原因失敗。第一步構(gòu)造函數(shù)有三種狀態(tài),。這個(gè)構(gòu)造函數(shù)我們可以先這樣寫(xiě)創(chuàng)建一個(gè)時(shí),首先進(jìn)行狀態(tài)初始化。所有的都是的,而并不是所有的對(duì)象都是。
? 從去年ES2015發(fā)布至今,已經(jīng)過(guò)去了一年多,ES2015發(fā)布的新的語(yǔ)言特性中最為流行的也就莫過(guò)于Promise了,Promise使得如今JavaScript異步編程如此輕松愜意,甚至慢慢遺忘了曾經(jīng)那不堪回首的痛楚。其實(shí)從JavaScript誕生,JavaScript中的異步編程就已經(jīng)出現(xiàn),例如點(diǎn)擊鼠標(biāo)、敲擊鍵盤(pán)這些事件的處理函數(shù)都是異步的,時(shí)間到了2009年,Node.js橫空出世,在整個(gè)Node.js的實(shí)現(xiàn)中,將回調(diào)模式的異步編程機(jī)制發(fā)揮的淋漓盡致,Node的流行也是的越來(lái)越多的JavaScripter開(kāi)始了異步編程,但是回調(diào)模式的副作用也慢慢展現(xiàn)在人們眼前,錯(cuò)誤處理不夠優(yōu)雅以及嵌套回調(diào)帶來(lái)的“回調(diào)地獄”。這些副作用使得人們從回調(diào)模式的溫柔鄉(xiāng)中慢慢清醒過(guò)來(lái),開(kāi)始尋找更為優(yōu)雅的異步編程模式,路漫漫其修遠(yuǎn)兮、吾將上下而求索。時(shí)間到了2015年,Promise拯救那些苦苦探索的先驅(qū)。行使它歷史使命的時(shí)代似乎已經(jīng)到來(lái)。
? 每個(gè)事物的誕生有他的歷史使命,更有其歷史成因,促進(jìn)其被那些探索的先驅(qū)們所發(fā)現(xiàn)。了解nodejs或者熟悉瀏覽器的人都知道,JavaScript引擎是基于事件循環(huán)或單線程這兩個(gè)特性的。更為甚者在瀏覽器中,更新UI(也就是瀏覽器重繪、重拍頁(yè)面布局)和執(zhí)行JavaScript代碼也在一個(gè)單線程中,可想而知,一個(gè)線程就相當(dāng)于只有一條馬路,如果一輛馬車(chē)拋錨在路上了阻塞了馬路,那么別的馬車(chē)也就擁堵在了那兒,這個(gè)單線程容易被阻塞是一個(gè)道理,單線程也只能允許某一時(shí)間點(diǎn)只能夠執(zhí)行一段代碼。同時(shí),JavaScript沒(méi)有想它的哥哥姐姐們那么財(cái)大氣粗,像Java或者C++,一個(gè)線程不夠,那么再加一個(gè)線程,這樣就能夠同時(shí)執(zhí)行多段代碼了,但是這樣就會(huì)帶來(lái)的隱患就是狀態(tài)不容易維護(hù),JavaScript選擇了單線程非阻塞式的方式,也就是異步編程的方式,就像上面的馬車(chē)拋錨在了路上,那么把馬車(chē)推到路邊的維修站,讓其他馬車(chē)先過(guò)去,等馬車(chē)修好了再回到馬路上繼續(xù)行駛,這就是單線程非阻塞方式。正如Promise的工作方式一樣,通過(guò)Promise去向服務(wù)器發(fā)起一個(gè)請(qǐng)求,畢竟請(qǐng)求有網(wǎng)絡(luò)開(kāi)銷(xiāo),不可能馬上就返回請(qǐng)求結(jié)果的,這個(gè)時(shí)候Promise就處于pending狀態(tài),但是其并不會(huì)阻塞其他代碼的執(zhí)行,當(dāng)請(qǐng)求返回時(shí),修改Promise狀態(tài)為fulfilled或者rejected(失敗請(qǐng)求)。同時(shí)執(zhí)行綁定到這兩個(gè)狀態(tài)上面的“處理函數(shù)”。這就是異步編程的模式,也就是Promise兢兢業(yè)業(yè)的工作方式,在下面一個(gè)部分將詳細(xì)討論P(yáng)romise。
? 怎么一句話解釋Promise呢?Promise可以代指那些尚未完成的一些操作,但是其在未來(lái)的某個(gè)時(shí)間會(huì)返回某一特定的結(jié)果。
? 當(dāng)創(chuàng)建一個(gè)Promise實(shí)例后,其代表一個(gè)未知的值,在將來(lái)的某個(gè)時(shí)間會(huì)返回一個(gè)成功的返回值,或者失敗的返回值,我們可以為這些返回值添加處理函數(shù),當(dāng)值返回時(shí),處理函數(shù)被調(diào)用。Promise總是處于下面三種狀態(tài)之一:
pending: Promise的初始狀態(tài),也就是未被fulfilled或者rejected的狀態(tài)。
fulfilled: 意味著promise代指的操作已經(jīng)成功完成。
rejected:意味著promise代指的操作由于某些原因失敗。
一個(gè)處于pending狀態(tài)的promise可能由于某個(gè)成功返回值而發(fā)展為fulfilled狀態(tài),也有可能因?yàn)槟承╁e(cuò)誤而進(jìn)入rejected狀態(tài),無(wú)論是進(jìn)入fulfilled狀態(tài)或者rejected狀態(tài),綁定到這兩種狀態(tài)上面的處理函數(shù)就會(huì)被執(zhí)行。并且進(jìn)入fulfilled或者rejected狀態(tài)也就不能再返回pending狀態(tài)了。
?
上面說(shuō)了那么多,其實(shí)都是鋪墊。接下來(lái)我們就開(kāi)始實(shí)現(xiàn)自己的Promise對(duì)象。go go go?。?!
Promise有三種狀態(tài),pending、fulfilled、rejected。
const PENDING = "PENDING" // Promise 的 初始狀態(tài) const FULFILLED = "FULFILLED" // Promise 成功返回后的狀態(tài) const REJECTED = "REJECTED" // Promise 失敗后的狀態(tài)
有了三種狀態(tài)后,那么我們?cè)趺磩?chuàng)建一個(gè)Promise實(shí)例呢?
const promise = new Promise(executor) // 創(chuàng)建Promise的語(yǔ)法
通過(guò)上面生成promise語(yǔ)法我們知道,Promise實(shí)例是調(diào)用Promise構(gòu)造函數(shù)通過(guò)new操作符生成的。這個(gè)構(gòu)造函數(shù)我們可以先這樣寫(xiě):
class Promise { constructor(executor) { this.status = PENDING // 創(chuàng)建一個(gè)promise時(shí),首先進(jìn)行狀態(tài)初始化。pending this.result = undefined // result屬性用來(lái)緩存promise的返回結(jié)果,可以是成功的返回結(jié)果,或失敗的返回結(jié)果 } }
我們可以看到上面構(gòu)造函數(shù)接受的參數(shù)executor。它是一個(gè)函數(shù),并且接受其他兩個(gè)函數(shù)(resolve和reject)作為參數(shù),當(dāng)resolve函數(shù)調(diào)用后,promise的狀態(tài)轉(zhuǎn)化為fulfilled,并且執(zhí)行成功返回的處理函數(shù)(不用著急后面會(huì)說(shuō)到怎么添加處理函數(shù))。當(dāng)reject函數(shù)調(diào)用后,promise狀態(tài)轉(zhuǎn)化為rejected,并且執(zhí)行失敗返回的處理函數(shù)。
現(xiàn)在我們的代碼大概是這樣的:
class Promise { constructor(executor) { this.status = PENDING this.result = undefined executor(data => resolveProvider(this, data), err => rejectProvider(this, err)) } } function resolveProvider(promise, data) { if (promise.status !== PENDING) return false promise.status = FULFILLED } function rejectProvider(promise, data) { if (promise.status !== PENDING) return false promise.status = FULFILLED }
Dont Repeat Yourselt?。?!我們可以看到上面代碼后面兩個(gè)函數(shù)基本相同,其實(shí)我們可以把它整合成一個(gè)函數(shù),在結(jié)合高階函數(shù)的使用。
const statusProvider = (promise, status) => data => { if (promise.status !== PENDING) return false promise.status = status promise.result = data } class Promise { constructor(executor) { this.status = PENDING this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } }
現(xiàn)在我們的代碼就看上去簡(jiǎn)潔多了。
其實(shí)通過(guò) new Promise(executor)已經(jīng)可以生成一個(gè)Promise實(shí)例了,甚至我們可以通過(guò)傳遞到executor中的resolve和reject方法來(lái)改變promise狀態(tài),但是!現(xiàn)在的promise依然沒(méi)啥卵用!??!因?yàn)槲覀儾](méi)有給它添加成功和失敗返回的處理函數(shù)。
首先我們需要給我們的promise增加兩個(gè)屬性,successListener和failureListener用來(lái)分別緩存成功處理函數(shù)和失敗處理函數(shù)。
class Promise { constructor(executor) { this.status = PENDING this.successListener = [] this.failureListener = [] this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } }
怎么添加處理函數(shù)呢?ECMASCRIPT標(biāo)準(zhǔn)中說(shuō)到,我們可以通過(guò)promise原型上面的then方法為promise添加成功處理函數(shù)和失敗處理函數(shù),可以通過(guò)catch方法為promise添加失敗處理函數(shù)。
const statusProvider = (promise, status) => data => { if (promise.status !== PENDING) return false promise.status = status promise.result = data switch(status) { case FULFILLED: return promise.successListener.forEach(fn => fn(data)) case REJECTED: return promise.failurelistener.forEach(fn => fn(data)) } } class Promise { constructor(executor) { this.status = PENDING this.successListener = [] this.failurelistener = [] this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } /** * Promise原型上面的方法 */ then(...args) { switch (this.status) { case PENDING: { this.successListener.push(args[0]) this.failurelistener.push(args[1]) break } case FULFILLED: { args[0](this.result) break } case REJECTED: { args[1](this.result) } } } catch(arg) { return this.then(undefined, arg) } }
我們現(xiàn)在的Promise基本初具雛形了。甚至可以運(yùn)用到一些簡(jiǎn)單的場(chǎng)景中了。舉個(gè)例子。
/*創(chuàng)建一個(gè)延時(shí)resolve的pormise*/ new Promise((resolve, reject) => {setTimeout(() => resolve(5), 2000)}).then(data => console.log(data)) // 5 /*創(chuàng)建一個(gè)及時(shí)resolve的promise*/ new Promise((resolve, reject) => resolve(5)).then(data => console.log(data)) // 5 /*鏈?zhǔn)秸{(diào)用then方法還不能夠使用!*/ new Promise(resolve=> resolve(5)).then(data => data).then(data => console.log(data)) // Uncaught TypeError: Cannot read property "then" of undefined
Promise需要實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,我們需要再次回顧下then方法的定義:
then方法為pormise添加成功和失敗的處理函數(shù),同時(shí)then方法返回一個(gè)新的promise對(duì)象,這個(gè)新的promise對(duì)象resolve處理函數(shù)的返回值,或者當(dāng)沒(méi)有提供處理函數(shù)時(shí)直接resolve原始的值。
可以看出,promise能夠鏈?zhǔn)秸{(diào)用歸功于then方法返回一個(gè)全新的promise,并且resolve處理函數(shù)的返回值,當(dāng)然,如果then方法的處理函數(shù)本身就返回一個(gè)promise,那么久不用我們自己手動(dòng)生成一個(gè)promise了。了解了這些,就開(kāi)始動(dòng)手寫(xiě)代碼了。
const isPromise = object => object && object.then && typeof object.then === "function" const noop = () => {} const statusProvider = (promise, status) => data => { // 同上面代碼 } class Promise { constructor(executor) { // 同上面代碼 } then(...args) { const child = new this.constructor(noop) const handler = fn => data => { if (typeof fn === "function") { const result = fn(data) if (isPromise(result)) { Object.assign(child, result) } else { statusProvider(child, FULFILLED)(result) } } else if(!fn) { statusProvider(child, this.status)(data) } } switch (this.status) { case PENDING: { this.successListener.push(handler(args[0])) this.failurelistener.push(handler(args[1])) break } case FULFILLED: { handler(args[0])(this.result) break } case REJECTED: { handler(args[1])(this.result) break } } return child } catch(arg) { return this.then(undefined, arg) } }
? 首先我們寫(xiě)了一個(gè)isPromise方法,用于判斷一個(gè)對(duì)象是否是promise。就是判斷對(duì)象是否有一個(gè)then方法,免責(zé)聲明為了實(shí)現(xiàn)上的簡(jiǎn)單,我們不區(qū)分thenable和promise的區(qū)別,但是我們應(yīng)該是知道。所有的promise都是thenable的,而并不是所有的thenable對(duì)象都是promise。(thenable對(duì)象是指帶有一個(gè)then方法的對(duì)象,該then方法其實(shí)就是一個(gè)executor。)isPromise的作用就是用于判斷then方法返回值是否是一個(gè)promise,如果是promise,就直接返回該promise,如果不是,就新生成一個(gè)promise并返回該promise。
? 由于需要鏈?zhǔn)秸{(diào)用,我們對(duì)successListener和failureListener中處理函數(shù)進(jìn)行了重寫(xiě),并不是直接push進(jìn)去then方法接受的參數(shù)函數(shù)了,因?yàn)閠hen方法需要返回一個(gè)promise,所以當(dāng)then方法里面的處理函數(shù)被執(zhí)行的同時(shí),我們也需要對(duì)then方法返回的這個(gè)promise進(jìn)行處理,要么resolve,要么reject掉。當(dāng)然,大部分情況都是需要resolve掉的,只有當(dāng)then方法沒(méi)有添加第二個(gè)參數(shù)函數(shù),同時(shí)調(diào)用then方法的promise就是rejected的時(shí)候,才需要把then方法返回的pormise進(jìn)行reject處理,也就是調(diào)用statusProvider(child, REJECTED)(data).
toy Promise實(shí)現(xiàn)的完整代碼:
const PENDING = "PENDING" // Promise 的 初始狀態(tài) const FULFILLED = "FULFILLED" // Promise 成功返回后的狀態(tài) const REJECTED = "REJECTED" // Promise 失敗后的狀態(tài) const isPromise = object => object && object.then && typeof object.then === "function" const noop = () => {} const statusProvider = (promise, status) => data => { if (promise.status !== PENDING) return false promise.status = status promise.result = data switch(status) { case FULFILLED: return promise.successListener.forEach(fn => fn(data)) case REJECTED: return promise.failurelistener.forEach(fn => fn(data)) } } class Promise { constructor(executor) { this.status = PENDING this.successListener = [] this.failurelistener = [] this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } /** * Promise原型上面的方法 */ then(...args) { const child = new this.constructor(noop) const handler = fn => data => { if (typeof fn === "function") { const result = fn(data) if (isPromise(result)) { Object.assign(child, result) } else { statusProvider(child, FULFILLED)(result) } } else if(!fn) { statusProvider(child, this.status)(data) } } switch (this.status) { case PENDING: { this.successListener.push(handler(args[0])) this.failurelistener.push(handler(args[1])) break } case FULFILLED: { handler(args[0])(this.result) break } case REJECTED: { handler(args[1])(this.result) break } } return child } catch(arg) { return this.then(undefined, arg) } }
在ECMAScript標(biāo)準(zhǔn)中,Promise構(gòu)造函數(shù)上面還提供了一些靜態(tài)方法,比如Promise.resolve、Promise.reject、Promsie.all、Promise.race。當(dāng)我們有了上面的基礎(chǔ)實(shí)現(xiàn)后,為我們的toy Promise添加上面這些新的功能一定能讓其更加實(shí)用。
在我們的基本實(shí)現(xiàn)中,我們并沒(méi)有區(qū)分thenable對(duì)象,其實(shí)Promise.resolve和then方法都可以接受一個(gè)thenable對(duì)象,并把該thenable對(duì)象轉(zhuǎn)化為一個(gè)promise對(duì)象,如果想讓我們的toy Promise用于生產(chǎn)的話,這也是要考慮的。
為了讓我們的toy Promise變得更強(qiáng)壯,我們需要擁有強(qiáng)健的錯(cuò)誤處理機(jī)制,比如驗(yàn)證executor必須是一個(gè)函數(shù)、then方法的參數(shù)只能是函數(shù)或者undefined或null,又比如executor和then方法中拋出的錯(cuò)誤并不能夠被window.onerror監(jiān)測(cè)到,而只能夠通過(guò)錯(cuò)誤處理函數(shù)來(lái)處理,這也是需要考慮的因素。
如果我們的Promise polyfill是考慮支持多平臺(tái),那么首要考慮的就是瀏覽器環(huán)境或Node.js環(huán)境,其實(shí)在這兩個(gè)平臺(tái),原生Promise都是支持兩個(gè)事件的。就拿瀏覽器端舉例:
unhandledrejection: 在一個(gè)事件循環(huán)中,如果我們沒(méi)有對(duì)promise返回的錯(cuò)誤進(jìn)行處理,那么就會(huì)在window對(duì)象上面觸發(fā)該事件。
rejectionhandled:如果在一個(gè)事件循環(huán)后,我們才去對(duì)promise返回的錯(cuò)誤進(jìn)行處理,那么就會(huì)在window對(duì)象上面監(jiān)聽(tīng)到此事件。
關(guān)于這兩個(gè)事件以及node.js平臺(tái)上面類似的事件請(qǐng)參考Nicholas C. Zakas新書(shū)
Promise能夠很棒的處理異步編程,要想學(xué)好它我認(rèn)為最好的方法就是親自動(dòng)手去實(shí)現(xiàn)一個(gè)自己的Promise,下面的項(xiàng)目Jocs/promise是我的實(shí)現(xiàn),歡迎大家pr和star。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/87913.html
摘要:但模型載入程序并不是同步執(zhí)行的載入文檔和幾何等動(dòng)作在里都是異步的,我們沒(méi)辦法知道哪一個(gè)模型是第一個(gè)被完整載入,和下個(gè)一個(gè)完全載入的是誰(shuí)而在一些應(yīng)用場(chǎng)景里是有可能需要在一個(gè)序列聚合多個(gè)模型。 showImg(https://segmentfault.com/img/bVVaPI?w=600&h=390); 此篇博客原著為 Autodesk ADN 的梁曉冬,以下以我簡(jiǎn)稱。 我的同事創(chuàng)作了...
摘要:初次寫(xiě)文章,請(qǐng)多多包涵我最近正在根據(jù)這本書(shū)從頭開(kāi)始實(shí)現(xiàn)了一遍的框架。筆記目錄鏈接個(gè)人認(rèn)為本書(shū)對(duì)于想了解框架源碼的讀者來(lái)說(shuō)相當(dāng)有用,完全值得去購(gòu)買(mǎi)這本書(shū)書(shū)本主頁(yè)。因?yàn)槭浅鯇W(xué)者,筆記里可能有一些錯(cuò)誤,我也會(huì)繼續(xù)修改。 (初次寫(xiě)文章,請(qǐng)多多包涵) 我最近正在根據(jù)《Build your own angularJS》這本書(shū)從頭開(kāi)始實(shí)現(xiàn)了一遍AngularJS的框架。我把相關(guān)的源碼和我的個(gè)人學(xué)習(xí)筆...
摘要:最近在看,打算跟著書(shū)中的代碼敲一遍,加深對(duì)的理解。在這里記錄過(guò)程中的問(wèn)題與心得。根據(jù)排查內(nèi)存耗盡應(yīng)該是這個(gè)版本的問(wèn)題,換成后問(wèn)題消失。因此認(rèn)為這種寫(xiě)法是有風(fēng)險(xiǎn)的,必須用頂上那一行注釋表明我確實(shí)要全局都的才行。不得不感嘆的嚴(yán)謹(jǐn)。 最近在看 build your own angularjs ,打算跟著書(shū)中的代碼敲一遍,加深對(duì)AngularJS的理解。在這里記錄過(guò)程中的問(wèn)題與心得。 Int...
摘要:新開(kāi)一個(gè)坑,起名為,自己造一些小輪子。之前貌似在知乎上看到一個(gè)問(wèn)題是說(shuō)如何使用實(shí)現(xiàn)它原生的和方法,今天我來(lái)實(shí)現(xiàn)一番。但是,這樣等于說(shuō)只給傳了一個(gè)數(shù)組參數(shù),并不能達(dá)到目的。同理來(lái)實(shí)現(xiàn)參考資料深入之和的模擬實(shí)現(xiàn) showImg(https://segmentfault.com/img/bVbbHCv?w=1123&h=629); 新開(kāi)一個(gè)坑,起名為【build your xxx】,自己造一...
今天來(lái)實(shí)現(xiàn)JavaScript的bind函數(shù)。首先看MDN的bind函數(shù)描述: 從上面可以看出來(lái),var A = B.bind(this)函數(shù)其實(shí)干了這幾件事情: 返回一個(gè)函數(shù),且這個(gè)函數(shù)后面運(yùn)行時(shí)的this就是bind(this)傳入的this 接收參數(shù),這些參數(shù)(如果有的話)作為bind()的第二個(gè)參數(shù)跟在this(或其他對(duì)象)后面,之后它們會(huì)被插入到目標(biāo)函數(shù)的參數(shù)列表的開(kāi)始位置,傳遞給綁定...
閱讀 540·2023-04-25 14:26
閱讀 1295·2021-11-25 09:43
閱讀 3489·2021-09-22 15:25
閱讀 1458·2019-08-30 15:54
閱讀 533·2019-08-30 12:57
閱讀 778·2019-08-29 17:24
閱讀 3174·2019-08-28 18:13
閱讀 2696·2019-08-28 17:52