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