前端基本功——面試必問系列(1):都2024了,還沒吃透Promise?一文搞懂
寫在前面:
大家好,我是山里看瓜,該系列文章是為了幫助大家不管面試還是開發對前端的一些基本但是很重要的知識點認識更加深入和全面。
想寫這個系列文章的初衷是:我發現前端的很多基本知識,使用起來很簡單,定義看起來也很簡單。很多人你在問他相關問題的時候,他也能說上幾句。但是為什么用?怎么用會更好?原理是什么?讓你實現你怎么做?這些問題很多人都是一知半解,某些知識點我本人也是如此,只知道去用,甚至有時候都不知道為什么用,更別說原理,秉承的原則就是程序跟我要么有一個能跑,至于怎么跑那雨我無瓜...
本篇我們從各個方面來介紹Promise,把一些你知道的不知道的點全都梳理一遍,讓你面試中講得透徹,表現亮眼;讓你在開發中使用更加知根知底。
我們將從以下幾個方面進行講解學習:

Promise是什么?
定義
- mdn描述:一個
Promise是一個代理,它代表一個在創建promise時不一定已知的值。它允許你將處理程序與異步操作的最終成功值或失敗原因關聯起來。這使得異步方法可以像同步方法一樣返回值:異步方法不會立即返回最終值,而是返回一個 promise,以便在將來的某個時間點提供該值。 - 總結來說:
Promise是ES6規范提出的一個技術,用于在JavaScript中進行異步編程(讀寫文件、數據庫、請求、定時器)的解決方案,原來的方案是使用回調嵌套的方式,容易造成回到地獄(后面我們會說到)。 - 具體來說:
- 語法上來說:
Promise是一個構造函數 - 功能上來說:
Promise對象用來封裝一個異步操作并可以獲取其成功或者失敗的值 - promise 中既可以異步任務,也可以時同步任務
- 語法上來說:
Promise的狀態
狀態是 promise 實例對象中的一個屬性:PromiseState,該屬性有三種值:
- 待定(pending):初始狀態,既沒有被兌現,也沒有被拒絕,待定狀態。
- 完成(resolved / fullfiled ):意味著操作成功完成。
- 失敗(rejected):意味著異步操作失敗。
狀態變化有且只有兩種情況:
pending變為resolved / fullfiled。pending變為rejected
狀態變化說明:
- 有且只會有這兩種變化情況,并且 promise 對象的狀態只會改變一次,從 pending 改變為成功或者失敗狀態。
- 無論狀態變為成功或者失敗,始終都會有一個結果數據(跟是否有返回無關)。
- 執行成功后的結果只一般稱為
value,執行失敗的結果值一般稱為reason。
Promise的結果
Promise 的結果屬性:
- 我們在實例對象身上能看到
PromiseResult這個屬性,它保存著異步任務執行成功或者失敗的結果。
怎么修改 promise 的結果?
- resolve 函數:修改為成功狀態(fullfiled)。
- reject 函數:修改為失敗狀態(rejected)。
Promise的工作流程

為什么要用Promise?
- 讓我們在處理異步操作時,能夠更加靈活地指定回調函數。
- 以前回調函數方式,必須在啟動異步任務前就指定回調函數;
- promise:啟動異步任務 ——> 返回 promise 對象 ——> 給 promise 對象綁定回調;
- promise方式,我們甚至可以在異步任務結束后再指定,并且可以指定多個回調。
- 支持鏈式調用,可以解決回調地獄問題。
- 回調地獄:回調函數嵌套調用,外部回調函數異步執行的結果是嵌套的回調執行的條件。
- 回調地獄的缺點:不利于閱讀,不利于異常處理,維護起來比較復雜。
如何使用Promise —— 方法參數詳細說明
Promise 構造器函數:Promise(executor) {}
- executor 函數:執行器 (resolve, reject) => {}
- resolve 函數: 內部定義成功時我們調用的函數 value => {}
- reject 函數:內部定義失敗時我們調用的函數 reason => {}
- 說明:executor 會在 Promise 內部立即同步調用,異步操作在執行器中執行,即執行器函數并不是異步執行
Promise 原型方法
- Promise.prototype.then 方法:(onResolved, onRejected) => {}
- onResolved 函數:成功的回調函數 (value) => {}
- onRejected 函數:失敗的回調函數 (reason) => {}
- then()總是返回一個新的promise
- 新promise的結果狀態由then指定的回調函數執行的結果決定
- 拋出錯誤
- 返回失敗的promise
- 返回成功的promise
- 返回其它任何值
- 說明:這兩個方法用于指定得到成功 value 的成功的回調和得到失敗 reason 的失敗的回調
- then 方法返回一個新的 promise對象
- Promise.prototype.catch 方法:(onRejected) => {}
- onRejected 函數:失敗的回調函數 (reason) => {}
Promise 構造函數本身的方法
- Promise.resolve 方法:(value) => {}
- value:成功的數據或 promise 對象
- 如果傳遞的參數為 非 promise 對象,則返回的結果為成功 promise 對象
- 如果傳入的參數為 Promise 對象,則參數的結果決定了 resolve 的結果
- 說明:返回一個成功/失敗的 promise 對象
let p1 = Promise.resolve(520)
let p2 = Promise.resolve(new Promise((resolve, reject) => {
resolve('OK')
})) // 這時 p2 狀態為成功,成功的值為 'OK'?
- Promise.reject 方法:(reason) =>{}
- reason:失敗的原因
- 說明:返回一個失敗的 promise 對象
let p = Promise.reject(520) // 無論傳入的是什么,返回的都是一個失敗的promise 對象
// 傳入什么,失敗的結果就是什么
- Promise.all 方法:(promises) => {}
- promises:包含 n 個 promise 的數組
- 批量/一次性發送多個異步請求
- 當都成功時, 返回的promise才成功
- 一旦有一個失敗的, 返回的promise就失敗了
- 說明:返回一個新的 promise,只有所有的 promise 都成功時才成功,只要有一個失敗了就直接失敗
- 成功的結果時每一個 promise 對象成功結果組成的數組(有順序)
- 失敗的結果是在這個數組中失敗的那個 promise 對象失敗的結果
let p1 = new Promise((resolve, reject) => {
resolve('OK')
})
let p2 = Promise.resolve('Success')
let p3 = Promise.resolve('Success')
const result = Promise.all([p1, p2, p3])
- Promise.race 方法:(promises) => {}
- promises:包含 n 個 promise 的數組
- race:賽跑/比賽
- 說明:返回一個新的promise,第一個完成的 promise 的結果狀態就是最終的結果狀態
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 1000)
})
let p2 = Promise.resolve('Success')
let p3 = Promise.resolve('Success')
const result = Promise.race([p1, p2, p3]) // =>結果為 p2 的結果,因為p2 先改變狀態
Promise 在開發中比較常用的技巧
我們在實際開發中,經常遇到需要發多個請求獲取數據,當這些請求之前并沒有相互依賴時,我們使用正常的 Promise 方式去請求或者使用 async await方式請求,都是順序執行,一個請求在前一個請求完成之后發起,這樣非常的低效率,并且性能和體驗都非常的差,我們依賴請求數據的頁面部分會有長時間空白,用戶體驗非常差。
這時候我們可以使用 Promise.all() + async、await來同時并發請求,這樣請求就可以同時發起,實現一個并行發出的效果。
Promise.all(promises) 方法的結果是一個包含所有異步操作的結果數組,能夠一一對應上 promises 數組中的異步操作。
以下是一個簡單示例:
// 請求接口數據的方法
const getApiData = async () {
const [res1, res2, res3] = await Promise.all(
[
Api.getData1(),
Api.getData2(),
Api.getData3(),
]
)
}
幾個注意點:
- 函數內部使用
await時,函數必須使用 async 關鍵字; - 只使用一個
await,給Promise.all()使用; - 內部請求不要加 await 關鍵字,否則還是會順序請求,不能實現并行發起;
- 因為
Promise.all()的結果是對應內部異步操作的數組,我們可以直接通過數組解構,獲取每個請求的結果,方便后續針對請求值做操作。
Promise風格方法封裝舉例
- fs 模塊封裝
function mineReadFile (path) {
return new Promise((resolve, reject) => {
// 讀取文件
require('fs').readFile(path, (err, data) => {
// 判斷
if (err) reject(err)
// 成功
resolve(data)
})
})
}
// 調用
mineReadFile("/file/test.txt").then(value => {
console.log(value)
}, reason => {
console.log(reason)
});
- Ajax 請求封裝
function sendAJAX(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
// 設置響應數據格式
xhr.responseType = 'json'
xhr.open('GET', url)
xhr.send();
// 處理結果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 判斷成功
if (xhr.status >= 200 && xhr.status < 300) {
// 成功的結果
resolve(xhr.response)
} else {
reject(xhr.status)
}
}
}
})
}
// 調用
sendAJAX('https://api.apiopen.top/getJoke')
.then(value => {
console.log(value)
}, reason => {
console.warn(reason)
})
手寫Promise
開始手寫之前我們需要先搞清楚 promise 的幾個關鍵問題:
-
如何改變 promise 的狀態?
- resolve(value):如果當前是 pending 就會變為 resolved
- reject(reason):如果當前是 pending 就會變為 rejected
- 拋出異常:如果當前是 pending 就會變為 rejected
-
一個 promise 指定(then方法)多個成功/失敗回調函數,都會調用嗎?
- 當 promise 改變為對應狀態時都會調用
let p = new Promise((resolve, reject) => { resolve('ok') // 這里狀態改變了,所以下邊兩個回調都會執行,如果狀態不改變,下面的回調都不執行 }) // 指定回調 - 1 p.then(value => { console.log(value) }) // 指定回調 - 2 p.then(value => { alert(value) }) -
改變 promise 狀態和指定回調函數的順序是什么樣的,誰先執行,誰后執行?
問題簡單描述:promise 代碼在運行時,resolve/reject改變狀態先執行,還是 then 方法指定回調先執行?
-
都有可能,正常情況下是先指定回調再改變狀態,但也可以先改變狀態再指定回調
-
當執行器函數中的任務是一個同步任務(直接調 resolve()/reject()) 的時候,先改變 promise 狀態,再去指定回調函數*
-
當執行器函數中的任務是一個異步任務的時候,then 方法先執行(指定回調),改變狀態后執行
// 這時是hen 方法先執行(指定回調),改變狀態后執行 let p = new Promise((resolve, reject) => { setTimeout(() => { resolve('OK') }, 1000) }) p.then(value => { console.log(value) })
-
-
如何先改狀態再指定回調?
- 在執行器中直接調用 resolve()/reject()
- 延遲更長時間才調用 then()
-
什么時候才能得到數據(回調函數什么時候執行)?
- 如果先指定的回調,那當狀態發生改變時(調用resolve()/reject()時),回調函數就會調用,得到數據
- 如果先改變的狀態,那當指定函數時(then 方法),回調函數就會調用,得到數據
-
-
promise.then() 返回的新 promise 的結果狀態有什么決定?
- 簡單表達:由 then() 指定的回調函數執行的結果決定
- 詳細表達:
- 如果拋出異常,新 promise 變為 rejected,reason 為拋出的異常
- 如果返回的是非 promise 的任意值,新 promise 變為 resolved,value 為返回的值
- 如果返回的時另一個新的 promise,此 promise 的結果就會成為新 promise的結果
-
promise 如何串聯多個操作任務?
- promise 的 then() 返回一個新的promise,可以看成 then() 的鏈式調用
- 通過 then 的鏈式調用串聯多個同步/異步任務
-
promise異常穿透?
- 當使用 promise 的 then 鏈式調用時,可以在最后指定失敗的回調,
- 前面任何操作除了異常,都會傳到最后失敗的回調中處理
-
中斷 promise 鏈
- 當使用 promise 的 then 鏈式調用時,在中間中斷,不再調用后面的回調函數
- 辦法:在回調函數中返回一個 pending 狀態的 promise 對象
let p = new Promise((resolve, reject) => { setTimeout(() => { resolve('OK') }, 1000) }) p.then(value => { console.log(111) return new Promise(() => {}) }).then(value => { console.log(222) })
函數方式:封裝成一個構造函數
function Promise(executor) {
// 添加屬性
this.PromiseState = 'pending'
this.PromiseResult = null
// 聲明屬性 因為實例對象不能直接調用onResolve跟onReject 所以下面then中需要先保存在callback里面
this.callbacks = []
// 保存實例對象的 this 的值
const self = this // 常見的變量名有self _this that
// resolve 函數
function resolve(data) {
// 判斷狀態
if (self.PromiseState !== 'pending') return
// console.log(this) => 這里的this指向window,下面用this的話時直接修改的window
// 1. 修改對象的狀態 (PromiseState)
self.PromiseState = 'fulfilled'
// 2. 設置對象結果值 (PromiseResult)
self.PromiseResult = data
// 調用成功的回調函數
setTimeout(() => {
self.callbacks.forEach((item) => {
item.onResolved(data)
})
})
}
// reject 函數
function reject(data) {
// 判斷狀態
if (self.PromiseState !== 'pending') return
// 1. 修改對象的狀態 (PromiseState)
self.PromiseState = 'rejected'
// 2. 設置對象結果值 (PromiseResult)
self.PromiseResult = data
// 調用失敗的回調函數
setTimeout(() => {
self.callbacks.forEach((item) => {
item.onRejected(data)
})
})
}
try {
// 同步調用【執行器函數】
executor(resolve, reject)
} catch (e) {
// 修改 promise 對象狀態
reject(e)
}
}
// 添加 then 方法
Promise.prototype.then = function (onResolved, onRejected) {
const self = this
// 判斷回調函數參數
if (typeof onRejected !== 'function') {
onRejected = (reason) => {
throw reason
}
}
if (typeof onResolved !== 'function') {
onResolved = (value) => value
}
return new Promise((resolve, reject) => {
// 封裝函數
function callback(type) {
try {
// 獲取回調函數的執行結果
let result = type(self.PromiseResult)
// 判斷
if (result instanceof Promise) {
result.then(
(v) => {
resolve(v)
},
(r) => {
reject(r)
}
)
} else {
// 結果的對象狀態為 【成功】
resolve(result)
}
} catch (e) {
reject(e)
}
}
// 調用回調函數 根據 PromiseState 去調用
if (this.PromiseState === 'fulfilled') {
setTimeout(() => {
callback(onResolved)
})
}
if (this.PromiseState === 'rejected') {
setTimeout(() => {
callback(onRejected)
})
}
// 判斷 pending 狀態
if (this.PromiseState === 'pending') {
// 保存回調函數
this.callbacks.push({
onResolved: function () {
callback(onResolved)
},
onRejected: function () {
callback(onRejected)
},
})
}
})
}
// 添加 catch 方法
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected)
}
// 添加 resolve 方法
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(
(v) => {
resolve(v)
},
(r) => {
reject(r)
}
)
} else {
// 狀態設置為成功
resolve(value)
}
})
}
// 添加 reject 方法
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
// 添加 all 方法
Promise.all = function (promises) {
// 聲明變量
let count = 0 // 計數
let arr = [] // 結果數組
// 遍歷
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
(v) => {
// 得知對象的狀態是成功
// 每個promise對象成功都加 1
count++
// 將當前每個promise對象成功的結果都存入到數組中
arr[i] = v
// 判斷
if (count === promises.length) {
// 修改狀態
resolve(arr)
}
},
(r) => {
reject(r)
}
)
}
})
}
// 添加 race 方法
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (var i = 0; i < promises.length; i++) {
promises[i].then(
(v) => {
// 修改返回對象的狀態為成功
resolve(v)
},
(r) => {
// 修改返回對象的狀態為成功
reject(r)
}
)
}
})
}
class 類的方式:封裝成一個類
// 封裝成類
class Promise {
//構造方法
constructor(executor) {
// 添加屬性
this.PromiseState = 'pending'
this.PromiseResult = null
// 聲明屬性 因為實例對象不能直接調用onResolve跟onReject 所以下面then中需要先保存在callback里面
this.callbacks = []
// 保存實例對象的 this 的值
const self = this // 常見的變量名有self _this that
// resolve 函數
function resolve(data) {
// 判斷狀態
if (self.PromiseState !== 'pending') return
// console.log(this) => 這里的this指向window,下面用this的話時直接修改的window
// 1. 修改對象的狀態 (PromiseState)
self.PromiseState = 'fulfilled'
// 2. 設置對象結果值 (PromiseResult)
self.PromiseResult = data
// 調用成功的回調函數
setTimeout(() => {
self.callbacks.forEach((item) => {
item.onResolved(data)
})
})
}
// reject 函數
function reject(data) {
// 判斷狀態
if (self.PromiseState !== 'pending') return
// 1. 修改對象的狀態 (PromiseState)
self.PromiseState = 'rejected'
// 2. 設置對象結果值 (PromiseResult)
self.PromiseResult = data
// 調用失敗的回調函數
setTimeout(() => {
self.callbacks.forEach((item) => {
item.onRejected(data)
})
})
}
try {
// 同步調用【執行器函數】
executor(resolve, reject)
} catch (e) {
// 修改 promise 對象狀態
reject(e)
}
}
// then 方法封裝
then(onResolved, onRejected) {
const self = this
// 判斷回調函數參數
if (typeof onRejected !== 'function') {
onRejected = (reason) => {
throw reason
}
}
if (typeof onResolved !== 'function') {
onResolved = (value) => value
}
return new Promise((resolve, reject) => {
// 封裝函數
function callback(type) {
try {
// 獲取回調函數的執行結果
let result = type(self.PromiseResult)
// 判斷
if (result instanceof Promise) {
result.then(
(v) => {
resolve(v)
},
(r) => {
reject(r)
}
)
} else {
// 結果的對象狀態為 【成功】
resolve(result)
}
} catch (e) {
reject(e)
}
}
// 調用回調函數 根據 PromiseState 去調用
if (this.PromiseState === 'fulfilled') {
setTimeout(() => {
callback(onResolved)
})
}
if (this.PromiseState === 'rejected') {
setTimeout(() => {
callback(onRejected)
})
}
// 判斷 pending 狀態
if (this.PromiseState === 'pending') {
// 保存回調函數
this.callbacks.push({
onResolved: function () {
callback(onResolved)
},
onRejected: function () {
callback(onRejected)
},
})
}
})
}
// catch 方法
catch(onRejected) {
return this.then(undefined, onRejected)
}
// resolve 方法
static resolve(value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(
(v) => {
resolve(v)
},
(r) => {
reject(r)
}
)
} else {
// 狀態設置為成功
resolve(value)
}
})
}
// reject 方法
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
// all 方法
static all(promises) {
// 聲明變量
let count = 0 // 計數
let arr = [] // 結果數組
// 遍歷
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
(v) => {
// 得知對象的狀態是成功
// 每個promise對象成功都加 1
count++
// 將當前每個promise對象成功的結果都存入到數組中
arr[i] = v
// 判斷
if (count === promises.length) {
// 修改狀態
resolve(arr)
}
},
(r) => {
reject(r)
}
)
}
})
}
//race 方法
static race(promises) {
return new Promise((resolve, reject) => {
for (var i = 0; i < promises.length; i++) {
promises[i].then(
(v) => {
// 修改返回對象的狀態為成功
resolve(v)
},
(r) => {
// 修改返回對象的狀態為成功
reject(r)
}
)
}
})
}
}
async和await
- async/await是消滅異步回調的終極武器(以同步的流程,書寫異步的代碼)
- 作用: 簡化promise對象的使用, 不用再使用then/catch來指定回調函數
- 但和Promise并不互斥
- 反而, 兩者相輔相成
- 執行async函數, 返回promise對象
- await相當于promise的then
- try...catch可捕獲異常, 相當于promise的catch
async 函數
- 函數的返回值為 promise 對象
- promise 對象的結果由 async 函數執行的返回值決定
await 表達式
- await 右側的表達式一般為 promise 對象,但也可以時其它的值
- 如果表達式是 promise 對象,await 返回的是 promise 成功的值
- 如果表達式是其它值,直接將此值作為 await 的返回值?
async 和 await結合使用示例:
// resource 1.html 2.html 3.html
const fs = require('fs')
// 回調函數的方式
fs.readFile('./resousrce/1.html', (err, data1) => {
if (err) throw err
fs.readFile('./resousrce/2.html', (err, data2) => {
if (err) throw err
fs.readFile('./resousrce/3.html', (err, data3) => {
if (err) throw err
console.log(data1 + data2 + data3)
})
})
})
// resource 1.html 2.html 3.html
const fs = require('fs')
const util = require('util')
const mineReadFile = util.pomiseify(fs.readFile)
// async 與 await 結合
async function main() {
try {
// 讀取第一個文件的內容
let data1 = await mineReadFile('./resourse/1.html')
let data2 = await mineReadFile('./resourse/2.html')
let data3 = await mineReadFile('./resourse/3.html')
console.log(data1 + data2 + data3)
}catch(e) {
console.log(e)
}
}
main()
// async 與 await 結合發送 Ajax 請求
function sendAJAX(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
// 設置響應數據格式
xhr.responseType = 'json'
xhr.open('GET', url)
xhr.send();
// 處理結果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 判斷成功
if (xhr.status >= 200 && xhr.status < 300) {
// 成功的結果
resolve(xhr.response)
} else {
reject(xhr.status)
}
}
}
})
}
// 段子接口地址:https://api.apiopen.top/getJoke
let btn = document.querySelector('#btn')
btn.addEventListener('click', async function () {
// 獲取段子信息
let duanzi = await sendAJAX('https://api.apiopen.top/getJoke')
console.log(duanzi)
})
寫在后面
前端的東西其實很多并不難,只是很多人很少去深究,去全面了解,大家都只是學個大概,會用就行;
本系列文章將會全面深入的帶你重新夯實前端基礎,把一些重要且常用的知識點深入講解;
希望看完文章的你能有所收貨,使用起來更加輕松,面試更加自如亮眼;
我相信能看到這里的人呢,都是想進步想成長的小伙伴,希望在工作小伙伴的升職加薪,在找工作的小伙伴面試順利,收割offer;
對你有幫助的話給作者點點關注吧,你的支持就是我創作的動力!Peace and love~~
音樂分享
不知道有沒有喜歡說唱的小伙伴,作者有個想法,每期文章最后分享一首覺得不錯的說唱,當然你覺得好聽的歌曲也可以評論區分享給大家。
本期歌曲:《ghost face》—— 法老
- 最喜歡的一句詞:一個窮孩子生活在有錢人的城市,嘗試用精神去對抗物質。

該系列文章是為了幫助大家不管面試還是開發對前端的一些基本但是很重要的知識點認識更加深入和全面。想寫這個系列文章的初衷是:我發現前端的很多基本知識。為什么用?怎么用會更好?原理是什么?很多人并不清楚
浙公網安備 33010602011771號