文章目錄
這個前端學習筆記是學習gitchat上的一個課程,這個課程的質量非常好,價格也不貴,非常時候前端入門的小夥伴們進階。
筆記不會涉及很多,主要是提取一些知識點,詳細的大家最好去過一遍教程,相信你一定會有很大的收穫
使用
我們看下微信小程序的請求API
wx.request({
url: 'test.php', // 僅爲示例,並非真實的接口地址
data: {
x: '',
y: ''
},
header: {
'content-type': 'application/json' // 默認值
},
success(res) {
console.log(res.data)
}
})
如果需要通過請求結果再次請求,就會出現回調地獄
wx.request({
url: 'test.php', // 僅爲示例,並非真實的接口地址
//...
success(res) {
wx.request({
url: 'test.php', // 僅爲示例,並非真實的接口地址
//...
success(res) {
console.log(res.data)
}
})
}
})
我們現在使用promise來改善這種寫法
const require = (url, data, method)=>{
return new Promise((res,rej)=>{
wx.request({
url,
data,
method,
head:{},
success:(data)=>{
res(data)
},
fail:(error)=>{
rej(error)
}
})
}})
}
// 多層請求嵌套
require('test.php', {data:'1'}, 'get').then((data)=>{
return require('test.php', data, 'get')
}).then((data)=>{
return require('test.php', data, 'get')
})// 可以繼續嵌套
從上面可知,promise 是一個構造函數,使用的是promise的實例。
接受一個函數參數,而這個函數的參數有2個,分別代表這成功回調和失敗回調
而then
接受的2個函數參數分別對應着上面的2個函數參數
function Promise(fn) {
// fn(onfulfilled, onrejected){}
}
Promise.prototype.then = function(onfulfilled, onrejected) {
}
在promise構造函數的時候,通過onfulfilled, onrejected
將任務時間放置到異步隊列,只要有一個觸發,另外一個就沒有用了,也是promise的唯一性,所以需要內部有2個值保存着這個函數。
promise還設置了一個狀態(pending,fulfilled,rejected
)
function Promise(fn) {
const self = this
this.status = 'pending'
this.value = null// 成功回調的值
this.reason = null// 失敗回調的原因
function resolve(value) {
self.value = value
}
function reject(reason) {
self.reason = reason
}
fn(resolve, reject)// 將then設置的函數回調傳給fn
}
// 爲了保證 onfulfilled、onrejected 能夠強健執行,我們爲其設置了默認值,其默認值爲一個函數元(Function.prototype)。
Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
onfulfilled(this.value)
onrejected(this.reason)
}
狀態完善
從promise題目來看看狀態的作用
let promise = new Promise((resolve, reject) => {
resolve('data')
reject('error')
})
promise.then(data => {
console.log(data)
}, error => {
console.log(error)
})
只會輸出data
,因爲只能執行一個回調,雖然函數裏調用了2個回調參數,但是promise只會執行第一個,這是因爲promise有一個狀態機制。
所以我們需要在內部加一個狀態,來控制回調的執行。
function Promise(fn) {
this.status = 'pending'// 初始狀態
this.value = null
this.reason = null
const resolve = value => {
if (this.status === 'pending') {// 如果用戶執行了 resolve 則改變狀態爲fulfilled
this.value = value
this.status = 'fulfilled'
}
}
const reject = reason => {
if (this.status === 'pending') {// 如果用戶執行了 reject 則改變狀態爲rejected
this.reason = reason
this.status = 'rejected'
}
}
fn(resolve, reject)
}
// 參數加多層判斷,如果傳入不是函數的話,將它轉換成函數
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
if (this.status === 'fulfilled') {// 避免在加載的時候就執行成功回調
onfulfilled(this.value)
}
if (this.status === 'rejected') {// 避免在加載的時候就執行失敗回調
onrejected(this.reason)
}
}
異步執行
從例子看看promise如何進行異步處理
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data')
}, 2000)
})
promise.then(data => {
console.log(data)
})
// 2秒之後輸出data
由於我們2秒之後才調用resolve
,所以promise2秒內狀態都沒有發生變化。雖然使用then,但是狀態一直是pending
,所以無法執行onfulfilled
,所以我們需要在內部中在promise改變狀態的時候,自己調用onfulfilled
function Promise(fn) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledFunc = Function.prototype
this.onRejectedFunc = Function.prototype
const resolve = value => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
// 等待執行
this.onFulfilledFunc(this.value)
}
}
const reject = reason => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
// 等待執行
this.onRejectedFunc(this.reason)
}
}
fn(resolve, reject)
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
if (this.status === 'pending') {
// 保存回調函數
this.onFulfilledFunc = onfulfilled
this.onRejectedFunc = onrejected
}
}
但是如果不是異步調用resolve的話,就會導致執行順序和原本promise的不一樣
let promise = new Promise((resolve, reject) => {
resolve('data')
})
promise.then(data => {
console.log(data)
})
console.log(1)
正常會輸出 1然後輸出 data,但是我們實現的沒有考慮這個情況,輸出是data然後是1
所以我們在promise內部同樣需要進行一個異步處理。
我們要知道,promise異步是微任務的,可以使用nodejs 的nextTick
或則mutationObserve
來模擬,我們這裏使用setTimeout
來實現。
const resolve = value => {
if (value instanceof Promise) {// 傳入的如果是promise 鏈模式
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledFunc(this.value)
}
})
}
const reject = reason => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedFunc(this.reason)
}
})
}
看一下到目前爲止的實現代碼:
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledFunc = Function.prototype
this.onRejectedFunc = Function.prototype
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledFunc(this.value)
}
})
}
const reject = reason => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedFunc(this.reason)
}
})
}
executor(resolve, reject)
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
if (this.status === 'pending') {
this.onFulfilledFunc = onfulfilled
this.onRejectedFunc = onrejected
}
}
這樣,promise的輸出就能夠異步進行了。
細節完善
因爲promise是支持鏈式添加then,所以我們需要返回一個promise作爲結果。
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data')
}, 2000)
})
promise.then(data => {
console.log(`1: ${data}`)
})
promise.then(data => {
console.log(`2: ${data}`)
})
輸出情況
//1: data
//2: data
這就需要將內部保存的onFulfilledFunc
變成一個棧保存,使用數組即可。
function Promise(fn) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledArray = []
this.onRejectedArray = []
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledArray.forEach(func => {// 按順序調用
func(value)
})
}
})
}
const reject = reason => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedArray.forEach(func => {// 按順序調用
func(reason)
})
}
})
}
fn(resolve, reject)
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
if (this.status === 'pending') {// 棧保存
this.onFulfilledArray.push(onfulfilled)
this.onRejectedArray.push(onrejected)
}
}
還有一個細節,如果傳入的函數有語法錯誤,promise的狀態會變成rejected
我們只需要使用try-catch
來執行函數,如果出錯就調用rejected
try {
fn(resolve, reject)
} catch(e) {
reject(e)
}
最終版本:
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledArray = []
this.onRejectedArray = []
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledArray.forEach(func => {
func(value)
})
}
})
}
const reject = reason => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedArray.forEach(func => {
func(reason)
})
}
})
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
if (this.status === 'pending') {
this.onFulfilledArray.push(onfulfilled)
this.onRejectedArray.push(onrejected)
}
}function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledArray = []
this.onRejectedArray = []
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledArray.forEach(func => {
func(value)
})
}
})
}
const reject = reason => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedArray.forEach(func => {
func(reason)
})
}
})
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
if (this.status === 'pending') {
this.onFulfilledArray.push(onfulfilled)
this.onRejectedArray.push(onrejected)
}
}
鏈式調用
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('rex')
}, 2000)
})
promise.then(data => {
console.log(data)
return `${data} next then`
})
.then(data => {
console.log(data)
})
這段代碼執行後,將會在 2 秒後輸出:rex
,緊接着輸出:rex next then。
初步實現
我們分解上一個例子,第一次綁定的then是
data => {
console.log(data)
return `${data} next then`
}
我們可以在then方法裏 添加一個返回新的promise
Promise.prototype.then = function(onfulfilled, onrejected) {
// promise2 將作爲 then 方法的返回值
let promise2
if (this.status === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 這個新的 promise2 resolved 的值爲 onfulfilled 的執行結果
let result = onfulfilled(this.value)
resolve(result)
}
catch(e) {
reject(e)
}
})
})
}
if (this.status === 'rejected') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 這個新的 promise2 reject 的值爲 onrejected 的執行結果
let result = onrejected(this.value)
resolve(result)
}
catch(e) {
reject(e)
}
})
})
}
if (this.status === 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(() => {
try {
let result = onfulfilled(this.value)
resolve(result)
}
catch(e) {
reject(e)
}
})
this.onRejectedArray.push(() => {
try {
let result = onrejected(this.reason)
resolve(result)
}
catch(e) {
reject(e)
}
})
})
}
}
前面2中狀態都好理解,pendding
這個狀態是如何讓promise能夠執行後續的then?
返回的新的promise2
其實將promise2
的then
執行函數保存在promise1
的異步任務裏去了,當promise1
執行結束的時候,promise2
的then
開始執行,添加到異步任務去,這樣就能夠執行第二個then
,同理promise2
調用then
會返回第三個的promise
繼續完善
如果用戶顯式返回promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('rex')
}, 2000)
})
promise.then(data => {
console.log(data)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${data} next then`)
}, 4000)
})
})
.then(data => {
console.log(data)
})
我們需要將result返回的結果進行判斷,一種是普通值,一種是promise的實例
爲此我們抽象出 resolvePromise 方法進行統一處理。改動已有實現爲
const resolvePromise = (promise2, result, resolve, reject) => {
}
這個函數接受四個參數:
- promise2: 返回的 Promise 實例
- result: onfulfilled 或者 onrejected 函數的返回值
- resolve: promise2 的 resolve 方法
- reject: promise2 的 reject 方法
const resolvePromise = (promise2, result, resolve, reject) => {
// 當 result 和 promise2 相等時,也就是說 onfulfilled 返回 promise2 時,進行 reject
// 避免死循環
if (result === promise2) {
reject(new TypeError('error due to circular reference'))
}
// 是否已經執行過 onfulfilled 或者 onrejected
let consumed = false
let thenable
if (result instanceof Promise) {
if (result.status === 'pending') {
result.then(function(data) {
resolvePromise(promise2, data, resolve, reject)
}, reject)
} else {
result.then(resolve, reject)
}
return
}
let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null)
// 如果返回的是疑似 Promise 類型
if (isComplexResult(result)) {
try {
thenable = result.then
// 如果返回的是 Promise 類型,具有 then 方法
if (typeof thenable === 'function') {
thenable.call(result, function(data) {
if (consumed) {
return
}
consumed = true
return resolvePromise(promise2, data, resolve, reject)
}, function(error) {
if (consumed) {
return
}
consumed = true
return reject(error)
})
}
else {
resolve(result)
}
} catch(e) {
if (consumed) {
return
}
consumed = true
return reject(e)
}
}
else {
resolve(result)
}
}
對於onfulfilled
函數返回的結果result
:如果result
非 Promise 實例,非對象,非函數類型,是一個普通值的話(上述代碼中isComplexResult
函數進行判斷),我們直接將 promise2 以該值 resolve
掉。
對於onfulfilled
函數返回的結果result
:如果result
含有then
屬性方法,我們稱該屬性方法爲 thenable
,說明result
是一個 Promise 實例,我們執行該實例的then
方法(既thenable
),此時的返回結果有可能又是一個 Promise 實例類型,也可能是一個普通值,因此還要遞歸調用resolvePromise
。
從例子看爲什麼要遞歸調用
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('rex')
}, 2000)
})
promise.then(data => {
console.log(data)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${data} next then`)
}, 4000)
})
.then(data => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${data} next then`)
}, 4000)
})
})
})
.then(data => {
console.log(data)
})
該段代碼將會在 2 秒是輸出:rex,10 秒時輸出:rex next then next then。
promise 穿透
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('rex')
}, 2000)
})
promise.then(null)
.then(data => {
console.log(data)
})
2秒後輸出rex
,這就是 Promise 穿透現象:
給 .then() 函數傳遞非函數值作爲其參數時,實際上會被解析成 .then(null),這時候的表現應該是:上一個 promise 對象的結果進行“穿透”,如果在後面鏈式調用仍存在第二個 .then() 函數時,將會獲取被穿透下來的結果。
其實很簡單,並且我們已經做到了。想想在 then() 方法的實現中:我們已經對 onfulfilled 和 onrejected 函數加上判斷:
Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error }
// ...
}
如果 onfulfilled 不是函數類型,則給一個默認值,該默認值是返回其參數的函數。onrejected 函數同理。這段邏輯,就是起到了實現“穿透”的作用。
promise靜態方法
- Promise.prototype.catch
- Promise.resolve,Promise.reject
- Promise.all
- Promise.race
Promise.prototype.catch 實現
Promise.prototype.catch 可以進行異常捕獲,它的典型用法:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('rex error')
}, 2000)
})
promise1.then(data => {
console.log(data)
}).catch(error => {
console.log(error)
})
會在 2 秒後輸出:rex error。
實現:
Promise.prototype.catch = function(catchFunc) {
return this.then(null, catchFunc)
}
Promise.prototype.resolve實現
Promise.resolve(value) 方法返回一個以給定值解析後的 Promise 實例對象。
Promise.resolve('data').then(data => {
console.log(data)
})
console.log(1)
先輸出 1 再輸出 data。
實現
Promise.resolve = function(value) {
return new Promise((resolve, reject) => {
resolve(value)
})
}
Promise.all 實現
Promise.all(iterable) 方法返回一個 Promise 實例,此實例在 iterable 參數內所有的 promise 都“完成(resolved)”或參數中不包含 promise 時回調完成(resolve);如果參數中 promise 有一個失敗(rejected),此實例回調失敗(reject),失敗原因的是第一個失敗 promise 的結果。
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('rex')
}, 2000)
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('rex')
}, 2000)
})
Promise.all([promise1, promise2]).then(data => {
console.log(data)
})
將在 2 秒後輸出:[“rex”, “rex”]。
Promise.all = function(promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new TypeError('The arguments should be an array!')
}
return new Promise((resolve, reject) => {
try {
let resultArray = []
const length = promiseArray.length
for (let i = 0; i <length; i++) {
promiseArray[i].then(data => {
resultArray.push(data)// 結果保存到數組
// 全部異步執行完畢,返回最終結果
if (resultArray.length === length) {
resolve(resultArray)
}
}, reject)
}
}
catch(e) {
reject(e)
}
})
}
這裏用閉包保存一個length長度,沒成功執行一個promise就加1,這裏沒有加1,但是是使用result結果長度來判斷,如果長度一樣則代表所有異步已經全部返回,知道所有異步執行完成,就執行promise.all的異步任務,把所有結果當作resolve的參數
Promise.race 實現
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas1')
}, 2000)
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas2')
}, 4000)
})
Promise.race([promise1, promise2]).then(data => {
console.log(data)
})
將會在 2 秒後輸出:lucas1,實現 Promise.race 爲:
Promise.race = function(promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new TypeError('The arguments should be an array!')
}
return new Promise((resolve, reject) => {
try {
const length = promiseArray.length
for (let i = 0; i <length; i++) {
promiseArray[i].then(resolve, reject)// 第一個執行成功的會調用resolve
}
}
catch(e) {
reject(e)
}
})
}
這裏使用 for 循環同步執行 promiseArray 數組中的所有 promise 實例 then 方法,第一個 resolve 的實例直接會觸發新 Promise(代碼中新 new 出來的) 實例的 resolve 方法。後續調用resolve就會失效了。