前提知識
高階函數
高階函數
知足如下條件之一:前端
-
函數的參數是一個 函數git
-
函數的返回值是一個函數github
閉包
定義: 有權訪問另外一個函數做用域中變量的函數(來源紅寶書
)編程
函數柯里化
柯里化(Currying),把接受多個參數的函數轉換成接受一個單一參數的函數設計模式
舉例
let add = function(x) { return function(y) { return x + y } } add(3)(4) // 7
實際開發若是須要用到 柯里化,推薦使用 lodash.curry數組
應用(類型判斷)
typeof
沒法判斷 對象 類型promise
constructor
判斷 是誰構造出來的瀏覽器
instanceof
判斷 誰是誰的實例(即引用類型)微信
Object.prototype.toString.call()
完美判斷閉包
function isType(type) { return function (content) { return Object.prototype.toString.call(content) === `[object ${type}]` } } let isString = isType('String') console.log(isString('132456')) // 函數柯里化 console.log(isType('Number')(132456))
AOP 面向切片編程
也成爲 裝飾者模式
定義:
指在不修改原有代碼的狀況下增長新功能
function sleep(who) { who?console.log(who + '睡覺'):console.log('睡覺') } Function.prototype.before = function (callback) { return (...args)=> {// args 傳入的參數數組 callback() args?this(...args):this() // 傳入參數 } } let Wash = sleep.before(function () { console.log('洗臉') }) Wash() // 洗臉 睡覺 Wash('我') // 洗臉 我睡覺
觀察者模式
訂閱發佈
有掙議
,有些人說這個不算是觀察者模式。我的以爲算是屬於
let e = { _obg:{}, // 事件 _callback:[], // 處理函數列表 on(callback) { // 訂閱 this._callback.push(callback) }, emit(key,value){ // 發佈 this._obg[key] = value this._callback.forEach(fn=>{ fn(this._obg) // 參數傳入 }) } } e.on(function (obj) { console.log('發佈一個') console.log(obj) }) setTimeout(function () { e.emit('name','琛') },1000)
舉一個簡單的例子,即微博,你關注了 A,A 發動態就會通知你。你和 A 沒有直接聯繫,經過 微博本身的調度來完成
觀察者模式
與發佈訂閱區別
發佈訂閱模式
是 二者之間沒有直接關係,經過實踐調度中心來完成。而觀察者模式
是相互依賴的,一個改變。另外一個也發生改變
例子
// 設計模式 觀察者模式 // 與發佈訂閱二者區別 // 發佈訂閱 是基於一箇中間管理 on 和 emit 沒有直接關係 // 觀察者模式 是 Observer and Observed 有直接關係 // 一個依賴改變,另外一個也改變 Vue class Observer { // 觀察者 constructor(name) { // 傳遞參數 this.name = name } update(baby){ console.log(this.name+'知道'+baby.name+baby.state) } } class Observed{ // 被觀察者 constructor(name) { this.name = name this.state = '開心' this.Observer = [] } addObserver(o){ this.Observer.push(o) } setState(state){ this.state = state this.Observer.forEach(o=>{ o.update(this) }) } } let baby = new Observed('寶寶') let dad = new Observer('爸爸') let mom = new Observer('媽媽') // 添加觀察者 baby.addObserver(dad) baby.addObserver(mom) // 設置狀態 baby.setState('不開心') baby.setState('開心')
進入正題
基本的 promise 使用
promise
用來解決異步
Promise 是一個天生的類 須要傳入一個函數 默認會當即執行
有三種狀態 ,成功(resolve) ,失敗(reject), 等待
基本用法
let a = new Promise((resolve, reject) => { // 這兩個方法能夠更改promise 狀態 // resolve()若是是這樣 下面的輸出爲 undefined resolve('成功') // reject('失敗') }) a.then((data)=>{ console.log(data) // 成功 },(err)=>{ console.log(err) // 失敗 })
resolve,reject 這兩個方法能夠改變狀態。若是是 resolve
,則走then
的第一個函數,reject
走then
的第二個函數。而且都將參數傳入。
**注意:**走成功了就不能夠走失敗,反之亦然
從 0 開始,手寫 Promise
明白了基本用法,咱們開始模仿一下Promise
。這裏使用ES6
語法來模仿。
這是 Promise 的規範
同步實現
首先,須要就收一個executor
,是一個函數。當即執行,有三那種狀態,裏面還有resolve
,reject
兩個參數,這兩個參數是函數,須要接收兩個參數。同時promise
還有then
方法
思路分析
// promise 同步 // 三種狀態的定義 const Pending = 'PENDING' const Success = 'SUCCESS' const Error = 'Error' // promise 的實現 class WritePromise { constructor(fn) { // 初始化 this.state = Pending // 狀態初始化 this.value = undefined // 成功信息 this.err = undefined // 錯誤信息 let resolve = (value)=>{ if (this.state === Pending){ this.value = value this.state = Success } } let reject = (err)=>{ if (this.state === Pending){ this.value = err this.state = Error } } // 可能會出錯 try { fn(resolve,reject) // 當即執行 }catch (e) { console.log(e) // 若是內部出錯 直接交給reject 方法向下傳遞 reject(e) } } then(onfulfilled,onrejected){ switch (this.state) { case Success: onfulfilled(this.value) break case Error: onrejected(this.err) break } } } // export default WritePromise 瀏覽器端 module.exports = WritePromise
在 new 的過程當中,執行constructor
,傳入的 resolve
orreject
,進行賦值和狀態改變。而後then
方法更具 state
的狀態進行不一樣的操做。onfulfilled
and onrejected
是傳入的操做函數
爲何要加 try catch
在使用過程當中,不只僅只有reject
能夠接收錯誤,也能夠手動拋出錯誤。這樣就reject
捕獲不到錯誤。因此要加上 try catch 。保證能夠正常運行
測試
let WritePromise = require('./WritePromise') let promise = new WritePromise((resolve, reject) => { // 1. resolve('成功') // 2. // reject('失敗') }) promise.then((data) => { // onfulfilled 成功 console.log(data) }, (err) => { // onrejected 失敗 console.log(err) }) // 輸出 1. 成功 2. 失敗
異步實現
你們會發現。若是在resolve
orreject
,執行異步代碼(例如定時器)。會發現沒有結果。這是由於咱們剛纔寫的都是同步代碼。如今要改一下,改爲異步的
這時候就用到咱們前面的知識了,發佈訂閱模式
思路
首先,咱們應該知道在constructor
中傳入的fn
,若是加上定時器的話,它的狀態state
不會發生任何改變。也就是一直處於等待狀態
, 因此並不會執行then
裏面的函數。因此咱們應該考慮一下當他處於等待
的時候。是否是應該吧傳入的函數存儲起來,等到上面執行resolve
orreject
的時候,再把這個函數執行。
實現
// promise 異步 const Pending = 'PENDING' const Success = 'SUCCESS' const Error = 'Error' class WritePromiseAsync { constructor(fn) { this.state = Pending this.value = undefined this.err = undefined // 回調函數的存儲 this.SuccessCal = [] this.ErrorCal = [] let resolve = (value)=>{ if (this.state === Pending){ this.value = value this.state = Success // 對回調函數進行變量 而後執行 this.SuccessCal.forEach((fn)=>fn()) } } let reject = (err)=>{ if (this.state === Pending){ this.value = err this.state = Error this.ErrorCal.forEach((fn)=>fn()) } } // 可能會出錯 try { fn(resolve,reject) // 當即執行 }catch (e) { console.log(e) // 若是內部出錯 直接交給reject 方法向下傳遞 reject(e) } } then(onfulfilled,onrejected){ switch (this.state) { case Success: onfulfilled(this.value) break case Error: onrejected(this.err) break case Pending: this.SuccessCal.push(()=>{ // 爲何要這樣寫 由於這樣能夠作一些邏輯 AOP // 這裏面能夠作一些邏輯 onfulfilled(this.value) }) this.ErrorCal.push(()=>{ onrejected(this.err) }) break } } } module.exports = WritePromiseAsync
在順一遍。建立對象以後,調用then
方法, 代碼開始執行,執行到then
的時候,發現沒有對應的狀態改變,就先把它存儲起來。等到定時器結束以後,在把全部的函數都執行一次
鏈式調用
-
每次調用返回的都是一個 新的 Promise 實例(這就是爲何能夠一直
then
) -
鏈式調用的參數經過返回值傳遞
// 第二點的 代碼解釋 let b = new Promise((resolve, reject) => { resolve('data') }).then((data)=>{ data = data + '132456' // then能夠返回一個值,若是是普通值。就會走到下一個then 的成功中 return data }).then((data)=>{ console.log(data) // 輸出 data132456 })
若是返回的不是普通值,是 promise,則會使用這個 promise 的結果
let b = new Promise((resolve, reject) => { resolve('data') }).then((data)=>{ data = data + '132456' return data }).then(()=>{ return new Promise((resolve, reject) => { resolve('我是promise 的返回') // 若是返回的是一個promise,那麼會採用這個promise的結果 }) }).then((data)=>{ console.log(data) // 輸出 我是promise 的返回 })
catch
用來捕獲 最近的且沒有捕獲的錯誤
let b = new Promise((resolve, reject) => { reject('data') }).then().catch(err=>{ // 捕獲錯誤 捕獲最近的沒有捕獲的錯誤 console.log(err+'catch') // datacatch // 注意 返回的也是undefined })
注意點
上述走的是成功
,失敗也同樣。但會有一個小坑。
let b = new Promise((resolve, reject) => { resolve('data') }).then(()=>{},err=>{ console.log(err) // 在失敗函數中若是返回的是一個普通值,也會走下一次then的成功中 // return undefined 至關於返回了一個這個 }).then((data)=>{ console.log(data+'success') // 這個會走 成功的值 輸出 underfinedsuccess },(err)=>{ console.log(err+'err') })
特別注意,這裏會常常有遺漏。
鏈式調用的手寫實現
接着上次的WritePromiseAsync
實現屢次 then 傳遞 思路
原版作法中,當連續調用then
方法的時候,會把上一次的結果傳遞給下一個then
。
上面說過每次調用then
方法會返回一個promise
實例。因此,咱們須要在調用then
方法的時候返回一個promise
的實例,而且接收到then
方法的結果。在傳遞給這個promise
// 多餘的我就不寫了,主要寫差別化 的 then方法 then(onfulfilled, onrejected) { let promise2 = new ChainPromise((resolve, reject) => { let x switch (this.state) { case Success: x = onfulfilled(this.value) resolve(x) break case Error: x = onrejected(this.value) reject(x) break case Pending: this.SuccessCal.push(() => { try { let x = onfulfilled(this.value) resolve(x) } catch (e) { reject(e) } }) this.ErrorCal.push(() => { try { let x = onrejected(this.err) reject(x) } catch (e) { reject(e) } }) break } }) return promise2 }
注意,調用的時候要把 then
的兩個函數都要寫上,不然會報錯(尚未處理)
這樣事後 就能夠實現 屢次 then 方法傳遞結果了
實現 返回 promise 思路
說一下上面得哪一個x
,咱們是直接把它返回給對應得處理方法,若是x
是一個promise
呢?按照原版得來講。咱們應該把這個promise
的結果做爲返回值來繼續傳遞。因此咱們應該對這個x
進行處理
建立一個方法solveX
,來處理x
。
function solveX(promise2, x, resolve, reject) { if (promise2 === x){ return reject(new TypeError('引用自己')) } if ((typeof x === 'object' && x != null)|| typeof x === 'function'){ // 處理promise try { let then = x.then if (typeof then === 'function'){ // 只能認定他是promise了 then.call(x,(data)=>{ console.log(data) resolve(data) },(err)=>{ reject(err) }) } else { resolve(x) } } catch (e) { reject(e) // 取值失敗 走err } }else { // 是一個普通值 resolve(x) } }
爲何要把promise2
傳進來呢?由於若是 x
就是promise2
呢?則會是一個死循環。
對x
進行判斷,若是是普通值,直接返回就能夠了。若是不是,咱們取then
方法(注意是方法,不是結果). 若是有這個方法,咱們就認定他是一個promise
(可能有人會說若是then
是一個空方法呢?,那也只能認定了,咱們最多隻能作到這種程度的判斷了。)
注意then
的 this 指向問題
關於 promise2 傳入問題
try { x = onfulfilled(this.value) resolvePromise(promise2, x, resolve, reject) // 須要用x 來比較promise2的值 // resolve() } catch (e) { // 一旦出錯,走下一個promise 的錯誤處理方法 reject(e) }
若是直接傳入promise2
的話,由於是同步的過程,在建立的時候promise2
尚未生成,因此會報錯。這時候咱們能夠加一個定時器,把它變成異步。這就解決了這個問題
then(onfulfilled, onrejected) { let promise2 = new ChainPromise((resolve, reject) => { let x switch (this.state) { case Success: setTimeout(() => { // 若是不加定時器,promise2獲取不到 try { x = onfulfilled(this.value) resolvePromise(promise2, x, resolve, reject) // 須要用x 來比較promise2的值 // resolve() } catch (e) { // 一旦出錯,走下一個promise 的錯誤處理方法 reject(e) } }, 0) // 實現以後要判斷 X 若是x是一個普通值,就正常返回。若是是一個promise 則把promise的執行結果做爲參數傳遞給 相應的處理函數 break case Error: setTimeout(() => { try { x = onrejected(this.err) // reject(x) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) break case Pending: this.SuccessCal.push(() => { try { let x = onfulfilled(this.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) this.ErrorCal.push(() => { try { let x = onrejected(this.err) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) break } }) return promise2 }
注意,即便寫的是0
,也不會當即執行。
解決 then 裏面繼續返回 promise
上面咱們寫了一個方法來處理promise
,只須要進行一個遞歸就能夠解決
/** * * @param promise2 * @param x * @param resolve * @param reject * @returns {*} */ function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError('引用自己')) } if ((typeof x === 'object' && x != null) || typeof x === 'function') { // 處理promise try { let then = x.then if (typeof then === 'function') { then.call(x, (data) => { // resolve(data) 將data從新放入這個函數。直到是一個普通值再進行返回 resolvePromise(promise2, data, resolve, reject) }, (err) => { // reject(err) resolvePromise(promise2, err, resolve, reject) }) } else { resolve(x) } } catch (e) { reject(e) // 取值失敗 走err } } else { // 是一個普通值 resolve(x) } }
解決 then 必須傳值
then(onfulfilled, onrejected) { onfulfilled = typeof onfulfilled === 'function'?onfulfilled:v=>v onrejected = typeof onrejected === 'function'?onrejected:err=>{throw err} ........ }
將兩個函數進行判斷。若是不是函數,默認賦一個函數
防止屢次調用
function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise #<Promise>--')) } let called // 添加一個變量進行控制 if ((typeof x === 'object' && x != null) || typeof x === 'function') { try { let then = x.then if (typeof then === 'function') { then.call(x, y => { if (called) return called = true resolvePromise(promise2, y, resolve, reject) }, r => { if (called) return // 若是發現被調用過 直接return called = true reject(r) }) } else { resolve(x) } } catch (e) { if (called) return called = true reject(e) } } else { resolve(x) } }
總結代碼
// promise 鏈式調用的實現 const PENDING = 'PENDING' const RESOLVED = 'RESOLVED ' const REJECTED = 'REJECTED' function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise #<Promise>--')) } let called if ((typeof x === 'object' && x != null) || typeof x === 'function') { try { let then = x.then if (typeof then === 'function') { then.call(x, y => { if (called) return called = true resolvePromise(promise2, y, resolve, reject) }, r => { if (called) return called = true reject(r) }) } else { resolve(x) } } catch (e) { if (called) return called = true reject(e) } } else { resolve(x) } } class Promise { constructor(executor) { this.status = PENDING this.value = undefined this.reason = undefined this.SuccessCal = [] this.ErrorCal = [] let resolve = value => { if (this.status === PENDING) { this.value = value this.status = RESOLVED this.SuccessCal.forEach(fn => fn()) } } let reject = reason => { if (this.status === PENDING) { this.reason = reason this.status = REJECTED this.ErrorCal.forEach(fn => fn()) } } try { executor(resolve, reject) } catch (e) { reject(e) } } then(onRESOLVED, onrejected) { onRESOLVED = typeof onRESOLVED === 'function' ? onRESOLVED : v => v onrejected = typeof onrejected === 'function' ? onrejected : err => { throw err } let promise2 = new Promise((resolve, reject) => { if (this.status === RESOLVED) { setTimeout(() => { // 若是不加定時器,promise2獲取不到 try { let x = onRESOLVED(this.value) resolvePromise(promise2, x, resolve, reject) // 須要用x 來比較promise2的值 } catch (e) { // 一旦出錯,走下一個promise 的錯誤處理方法 reject(e) } }, 0) // 實現以後要判斷 X 若是x是一個普通值,就正常返回。若是是一個promise 則把promise的執行結果做爲參數傳遞給 相應的處理函數 } if (this.status === REJECTED) { setTimeout(() => { try { let x = onrejected(this.reason) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) } if (this.status === PENDING) { this.SuccessCal.push(() => { // 爲何要這樣寫 由於這樣能夠作一些邏輯 // 這裏面能夠作一些邏輯 setTimeout(() => { try { let x = onRESOLVED(this.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) }) this.ErrorCal.push(() => { setTimeout(() => { try { let x = onrejected(this.reason) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) }) } }) return promise2 } } // 測試 須要測試再添加 Promise.defer = Promise.deferred = function () { let dfd = {} dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve dfd.reject = reject }) return dfd } module.exports = Promise
關於符合性測試
這個是測試工具的github
安裝以後, 執行
npx promises-aplus-tests promise.js
爲何是npx
? 我沒有全局安裝
所有經過
測試結果.png
本文分享自微信公衆號 - 阿琛前端成長之路(lwkWyc)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。