摘要:其次要記錄狀態(tài),判斷消息是否已被發(fā)布,如果未發(fā)布消息,則通過(guò)來(lái)注冊(cè)回調(diào)時(shí),是將回調(diào)函數(shù)添加到內(nèi)部的回調(diào)隊(duì)列中如果消息已發(fā)布,則通過(guò)來(lái)注冊(cè)回調(diào)時(shí),直接將消息傳至回調(diào)函數(shù),并執(zhí)行規(guī)范中采用的狀態(tài)機(jī)制是可以轉(zhuǎn)化為或,并且只能轉(zhuǎn)化一次。
一點(diǎn)感悟
Promise 是編寫(xiě)異步的另一種方式,鄙人愚見(jiàn),它就是 Callback 的一種封裝
相比 Callback ,它有以下特點(diǎn)
Promise 將異步結(jié)果保存起來(lái),可以隨時(shí)獲取
鏈?zhǔn)秸{(diào)用 then 方法會(huì)返回一個(gè)新的 Promise ,從而避免了回調(diào)地獄
決定一次異步有兩個(gè)環(huán)節(jié)
發(fā)起異步事件
處理異步結(jié)果
Promise 可以給一個(gè)異步事件注冊(cè)多個(gè)處理函數(shù),舉個(gè)栗子,就像這樣
let p1 = new Promise((resolve) => { fs.readFile("./test.js", "utf8", (err, data) => { resolve(data) }) }) p1.then(data => console.log(data)) p1.then(data => console.log(data.toUpperCase()))
用 Callback 實(shí)現(xiàn)一樣的效果
用 callbacks 將所有注冊(cè)的函數(shù)保存
待異步事件返回結(jié)果,再遍歷 callbacks ,依次執(zhí)行所有注冊(cè)的函數(shù)
就像這樣
let callbacks = [] function resolve(data){ callbacks.forEach(cb => cb(data)) } fs.readFile("./test.js", "utf8", (err, data) => { resolve(data) }) callbacks.push(data => console.log(data)) callbacks.push(data => console.log(data.toUpperCase()))
將上述代碼封裝一下
const fs = require("fs") class FakePromise { constructor(fn){ this.callbacks = [] resolve = resolve.bind(this) function resolve(data){ this.callbacks.forEach(cb => cb(data)) } fn(resolve) } then(onFulfilled){ this.callbacks.push(onFulfilled) } } let p1 = new FakePromise(resolve => { fs.readFile("./test.js", "utf8", (err, data) => { resolve(data) }) }) p1.then(data => console.log(data)) p1.then(data => console.log(data.toUpperCase()))
哈?是不是和真的 Promise 有點(diǎn)像
從發(fā)布-訂閱模式的角度來(lái)看:
FakePromise 中通過(guò) .then(onFulfilled) 來(lái)訂閱消息,注冊(cè)處理異步結(jié)果的函數(shù)
通過(guò) resolve(data) 來(lái)發(fā)布消息,觸發(fā)處理異步結(jié)果的函數(shù)去執(zhí)行,發(fā)布的時(shí)機(jī)是異步事件完成時(shí)
延時(shí) resolve先前的代碼存在一個(gè)問(wèn)題,如果在執(zhí)行 p1.then(data => console.log(data)) 之前,resolve(data) 就已經(jīng)執(zhí)行了,那么再通過(guò) .then(onFulfilled) 注冊(cè)的處理異步結(jié)果的函數(shù)將永遠(yuǎn)不會(huì)執(zhí)行
為了避免這種情況,改造 resolve 函數(shù),在其內(nèi)部添加 setTimeout,從而保證那些注冊(cè)的處理函數(shù)是在下一個(gè)事件隊(duì)列中執(zhí)行,就像這樣
function resolve(value) { setTimeout(() => { this.callbacks.forEach(cb => cb(value)) }, 0) }
通過(guò)延時(shí)執(zhí)行 resolve 內(nèi)部的函數(shù),保證了先訂閱消息,再發(fā)布消息
但是 Promise 還有個(gè)額外的功能是在發(fā)布消息后,仍然可以訂閱消息,并且立即執(zhí)行,就像這樣
const fs = require("fs") let p1 = new Promise(resolve => { fs.readFile("./test.js", "utf8", (err, data) => resolve(data)) }) p1.then(data => console.log(data)) setTimeout(function(){ p1.then(data => console.log(data.toUpperCase())) }, 5000)
5s之內(nèi),文件早已讀取成功,但是在5s之后,依然可以通過(guò) .then 注冊(cè)處理事件,并且該事件會(huì)立即執(zhí)行
先發(fā)布,再訂閱實(shí)現(xiàn)先發(fā)布,再訂閱的基礎(chǔ)是將消息保存下來(lái)。其次要記錄狀態(tài),判斷消息是否已被發(fā)布,如果未發(fā)布消息,則通過(guò) .then 來(lái)注冊(cè)回調(diào)時(shí),是將回調(diào)函數(shù)添加到內(nèi)部的回調(diào)隊(duì)列中;如果消息已發(fā)布,則通過(guò) .then 來(lái)注冊(cè)回調(diào)時(shí),直接將消息傳至回調(diào)函數(shù),并執(zhí)行
Promise 規(guī)范中采用的狀態(tài)機(jī)制是 pending、fulfilled、rejected
pending 可以轉(zhuǎn)化為 fulfilled 或 rejected ,并且只能轉(zhuǎn)化一次。
轉(zhuǎn)化為 fulfilled 和 rejected 后,狀態(tài)就不可再變
修改代碼如下
class FakePromise { constructor(fn) { this.value = null this.state = "pending" this.callbacks = [] resolve = resolve.bind(this) function resolve(value) { setTimeout(() => { this.value = value this.state = "fulfilled" this.callbacks.forEach(cb => cb(value)) }, 0) } fn(resolve) } then(onFulfilled) { if (this.state === "pending") { this.callbacks.push(onFulfilled) } else { onFulfilled(this.value) } } }
既然實(shí)現(xiàn)了先發(fā)布,再訂閱,那么 resolve 中的 setTimeout 是不是可以去掉了?
并不可以,因?yàn)槿思艺?jīng)的 Promise 是這樣的
let p1 = new Promise(resolve => { resolve("haha") }) p1.then(data => console.log(data)) p1.then(data => console.log(data.toUpperCase())) console.log("xixi") // xixi // haha // HAHA
只有保留 resolve 中 setTimeout 才能使 FakePromise 實(shí)現(xiàn)相同的效果
let p1 = new FakePromise(resolve => { resolve("haha") }) p1.then(data => console.log(data)) p1.then(data => console.log(data.toUpperCase())) console.log("xixi") // xixi // haha // HAHA
沒(méi)有 setTimeout 的輸出結(jié)果
// haha // HAHA // xixi鏈?zhǔn)?Promise
正經(jīng)的 Promise 可以鏈?zhǔn)秸{(diào)用,從而避免了回調(diào)地獄
let p1 = new Promise(resolve => { fs.readFile("./test.js", "utf8", (err, data) => { resolve(data) }) }).then(res => { return new Promise(resolve => { fs.readFile("./main.js", "utf8", (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) })
正經(jīng)的 Promise 調(diào)用 then 方法會(huì)返回一個(gè)新的 Promise 對(duì)象
我們偽造的 FakePromise 并沒(méi)有實(shí)現(xiàn)這一功能,原來(lái)的 then 方法
... then(onFulfilled){ if (this.state === "pending") { this.callbacks.push(onFulfilled) } else { onFulfilled(this.value) } } ...
原來(lái)的 then 方法就是根據(jù) state 判斷是注冊(cè) onFulfilled 函數(shù),還是執(zhí)行 onFulfilled 函數(shù)
為了實(shí)現(xiàn) FakePromise 的高仿,我們要改造 then 方法,使其返回一個(gè)新的 FakePromise ,為了方便區(qū)分,將返回的 FakePromise 取名為 SonFakePromise ,而先前調(diào)用 then 的對(duì)象為 FatherFakePromise
那么問(wèn)題來(lái)了
那么構(gòu)造這個(gè) SonFakePromise 的函數(shù)參數(shù)是什么
這個(gè) SonFakePromise 什么時(shí)候 resolve ?
首先,當(dāng)構(gòu)造一個(gè)新的 SonFakePromise 時(shí),會(huì)將傳入的函數(shù)參數(shù) fn 執(zhí)行一遍,且這個(gè)函數(shù)有 resolve 參數(shù)
... then(onFulfilled){ if(this.state === "pending"){ this.callbacks.push(onFulfilled) let SonFakePromise = new FakePromise(function fn(resolve){ }) return SonFakePromise }else{ onFulfilled(this.value) let SonFakePromise = new FakePromise(function fn(resolve){ }) return SonFakePromise } } ...
現(xiàn)在的問(wèn)題是這個(gè) SonFakePromise 什么時(shí)候 resolve ?即構(gòu)造函數(shù)中的函數(shù)參數(shù) fn 如何定義
結(jié)合正經(jīng) Promise 的例子來(lái)看
let faherPromise = new Promise(resolve => { fs.readFile("./test.js", "utf8", (err, data) => { resolve(data) }) }).then(res => { return new Promise(resolve => { fs.readFile("./main.js", "utf8", (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) }) // 等同于 let faherPromise = new Promise(resolve => { fs.readFile("./test.js", "utf8", (err, data) => { resolve(data) }) }) let sonPromise = faherPromise.then(function onFulfilled(res){ return new Promise(function fn(resolve){ fs.readFile("./main.js", "utf8", (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) })
在例子中,onFulfilled 函數(shù)如下,且其執(zhí)行后返回一個(gè)新的 Promise,暫時(shí)取名為 fulPromise
function onFulfilled(res) { return new Promise(function fn(resolve){ fs.readFile("./main.js", "utf8", (err, data) => { resolve(data) }) }) }
現(xiàn)在來(lái)分析一下,fatherPromise,sonPromise 和 fulPromise 這三者的關(guān)系
sonPromise 是調(diào)用 fatherPromise 的 then 方法返回的
而調(diào)用這個(gè) then 方法需要傳入一個(gè)函數(shù)參數(shù),取名為 retFulPromise
retFulPromise 函數(shù)執(zhí)行的返回值 fulPromise
希望下面的代碼能有助于理解
let fatherPromise = new Promise(function fatherFn(fatherResolve){ fs.readFile("./test.js", "utf8", (err, data) => { fatherResolve(data) }) }) let sonPromise = fatherPromise.then(retFulPromise) function retFulPromise(res) { let fulPromise = new Promise(function fulFn(fulResolve){ fs.readFile("./main.js", "utf8", (err, data) => { fulResolve(data) }) }) return fulPromise }
fatherPromise 的狀態(tài)為 fulfilled 時(shí),會(huì)執(zhí)行 retFulPromise,其返回 fulPromise ,當(dāng)這個(gè) fulPromise 執(zhí)行 fulResolve 時(shí),即完成讀取 main.js 時(shí), sonPromise 也會(huì)執(zhí)行內(nèi)部的 resolve
所以可以看成,sonPromise 的 sonResolve 函數(shù),也被注冊(cè)到了 fulPromise 上
So,了解了整個(gè)流程,該怎么修改自己的 FakePromise 呢?
秀操作,考驗(yàn)技巧的時(shí)候到了,將 sonResolve 的引用保存起來(lái),注冊(cè)到 fulFakePromise 上
const fs = require("fs") class FakePromise { constructor(fn) { this.value = null this.state = "pending" this.callbacks = [] resolve = resolve.bind(this) function resolve(value) { setTimeout(() => { this.value = value this.state = "fulfilled" this.callbacks.forEach(cb => { let returnValue = cb.onFulfilled(value) if (returnValue instanceof FakePromise) { returnValue.then(cb.sonResolveRes) } }) }) } fn(resolve) } then(onFulfilled) { if (this.state === "pending") { let sonResolveRes = null let sonFakePromise = new FakePromise(function sonFn(sonResolve) { sonResolveRes = sonResolve }) this.callbacks.push({ sonFakePromise, sonResolveRes, onFulfilled }) return sonFakePromise } else { let value = onFulfilled(this.value) let sonResolveRes = null let sonFakePromise = new FakePromise(function sonFn(sonResolve) { sonResolveRes = sonResolve }) if (value instanceof FakePromise) { value.then(sonResolveRes) } return sonFakePromise } } }多角度測(cè)試
let fatherFakePromise = new FakePromise(resolve => { fs.readFile("./test.js", "utf8", (err, data) => { resolve(data) }) }) let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) { return new FakePromise(function fn(resolve) { fs.readFile("./main.js", "utf8", (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) })
let fatherFakePromise = new FakePromise(resolve => { fs.readFile("./test.js", "utf8", (err, data) => { resolve(data) }) }) setTimeout(function () { let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) { return new FakePromise(function fn(resolve) { fs.readFile("./main.js", "utf8", (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) }) }, 1000)
let fatherFakePromise = new FakePromise(resolve => { resolve("haha") }) let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) { return new FakePromise(function fn(resolve) { fs.readFile("./main.js", "utf8", (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) })
let fatherFakePromise = new FakePromise(resolve => { resolve("haha") }) setTimeout(function () { let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) { return new FakePromise(function fn(resolve) { fs.readFile("./main.js", "utf8", (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) }) }, 1000)參考資料
30分鐘,讓你徹底明白Promise原理
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107249.html
摘要:據(jù)調(diào)研機(jī)構(gòu)數(shù)據(jù),年第三季度,全球智能手機(jī)芯片市場(chǎng)占有率中,聯(lián)發(fā)科力壓高通,歷史首次登頂全球第一。年月,聯(lián)發(fā)科發(fā)布全球首款十核處理器,以及它的升級(jí)版。聯(lián)發(fā)科本月表示,其最新的旗艦芯片將于明年第一季度發(fā)布,希望在農(nóng)歷新年前推出。在被喊了一年的MTK YES后,聯(lián)發(fā)科終于迎來(lái)了自己的YES時(shí)刻。據(jù)調(diào)研機(jī)構(gòu)Counterpoint數(shù)據(jù),2020年第三季度,全球智能手機(jī)芯片市場(chǎng)占有率中,聯(lián)發(fā)科力壓高通...
摘要:以前實(shí)習(xí)的時(shí)候因?yàn)橼s時(shí)間直接用的插件做了個(gè)折疊菜單,對(duì)于一個(gè)原生控來(lái)說(shuō)還是更傾向于自己寫(xiě)一個(gè),畢竟為了個(gè)折疊菜單引入和有點(diǎn)太臃腫了。原版的效果其實(shí)也不難,主要是在開(kāi)合的過(guò)程中添加了的過(guò)渡效果。 以前實(shí)習(xí)的時(shí)候因?yàn)橼s時(shí)間直接用bootstrap的插件collapse.js做了個(gè)折疊菜單, 對(duì)于一個(gè)原生控來(lái)說(shuō)還是更傾向于自己寫(xiě)一個(gè), 畢竟為了個(gè)折疊菜單引入jq和bootstrap有點(diǎn)太臃腫...
摘要:一反射機(jī)制概念程序運(yùn)行時(shí),允許改變程序結(jié)構(gòu)或變量類(lèi)型,這種語(yǔ)言稱(chēng)為動(dòng)態(tài)語(yǔ)言,如,是動(dòng)態(tài)語(yǔ)言顯然,,不是動(dòng)態(tài)語(yǔ)言,但是有著一個(gè)非常突出的動(dòng)態(tài)相關(guān)機(jī)制。相關(guān)的為二獲取源頭重點(diǎn)打開(kāi)權(quán)限所有類(lèi)的對(duì)象其實(shí)都是的實(shí)例。 一、Java反射機(jī)制概念 程序運(yùn)行時(shí),允許改變程序結(jié)構(gòu)或變量類(lèi)型,這種語(yǔ)言稱(chēng)為動(dòng)態(tài)語(yǔ)言,如Python, Ruby是動(dòng)態(tài)語(yǔ)言;顯然C++,Java,C#不是動(dòng)態(tài)語(yǔ)言,但是JAVA有...
摘要:前言是一個(gè)優(yōu)秀的前端庫(kù),封裝了很多底層的實(shí)現(xiàn),可以用來(lái)制作游戲,場(chǎng)景等。今年月新發(fā)布了,到今天為止已經(jīng)更新到了。聲明本游戲來(lái)自于小站的官方教程,加入了一些個(gè)人的注釋?zhuān)疚闹荚趲椭魑挥^眾老爺快速上手。 前言 phaser是一個(gè)優(yōu)秀的前端canvas庫(kù),封裝了很多底層的實(shí)現(xiàn),可以用來(lái)制作游戲,h5場(chǎng)景等。今年1月新發(fā)布了phaser3,到今天為止已經(jīng)更新到了3.30。 聲明 本游戲來(lái)自于...
閱讀 1316·2021-11-22 09:34
閱讀 2178·2021-10-08 10:18
閱讀 1737·2021-09-29 09:35
閱讀 2471·2019-08-29 17:20
閱讀 2149·2019-08-29 15:36
閱讀 3412·2019-08-29 13:52
閱讀 791·2019-08-29 12:29
閱讀 1196·2019-08-28 18:10