JavaScript中異步編程的實現(xiàn)方式詳細講解
一:回調(diào)函數(shù)
通過將函數(shù)作為參數(shù)傳遞到異步任務(wù)中,在任務(wù)完成后執(zhí)行回調(diào)。
優(yōu)點:實現(xiàn)簡單,兼容性極佳(支持所有JS環(huán)境)
缺點:嵌套過深時會導致“回調(diào)地獄”,代碼可讀性和可維護性差
示例:
function fetchData(callback) {
setTimeout(() => callback("數(shù)據(jù)"), 1000);
}
fetchData(data => console.log(data));
二:事件監(jiān)聽
異步任務(wù)的執(zhí)行由事件觸發(fā),通過addEventListener綁定回調(diào)。
優(yōu)點:支持多事件綁定,實現(xiàn)模塊化解耦
支持多事件綁定
一個目標對象(如DOM元素)可同時綁定多個不同類型的事件(click/mouseover等),> 彼此互不干擾。
代碼組織更靈活,例如:
element.addEventListener(‘click’, handleClick);
element.addEventListener(‘mouseenter’, showTooltip);
模塊化解耦
生產(chǎn)者-消費者分離:事件觸發(fā)方(如按鈕)無需知道誰在處理事件,只需發(fā)出事件。
低耦合架構(gòu):不同模塊通過事件通信,避免直接相互調(diào)用。例如:
// 模塊A:發(fā)布事件
document.dispatchEvent(new CustomEvent(‘dataLoaded’, { detail: data }));
// 模塊B:訂閱事件(無需知道模塊A的存在)
document.addEventListener(‘dataLoaded’, (e) => updateUI(e.detail));
缺點:流程控制不直觀,需依賴事件驅(qū)動架構(gòu)
document.addEventListener('click', () => console.log("事件觸發(fā)"));
三:發(fā)布訂閱模式
通過事件中心調(diào)度訂閱者和發(fā)布者,實現(xiàn)完全解耦
優(yōu)點:支持多對多通信,擴展性強
缺點:需手動管理訂閱關(guān)系,調(diào)試復雜度較高
【本菜鳥還不太熟悉,后續(xù)總結(jié)】
四、Promise【詳解】
Promise是異步編程的一種解決方案,Promise 是一個構(gòu)造函數(shù),接收一個函數(shù)作為參數(shù),返回一個 Promise 實例。一個 Promise 實例有三種狀態(tài),分別是pending、resolved 和 rejected,分別代表了進行中、已成功和已失敗。實例的狀態(tài)只能由 pending 轉(zhuǎn)變 resolved 或者rejected 狀態(tài),并且狀態(tài)一經(jīng)改變,就凝固了,無法再被改變了。狀態(tài)的改變是通過 resolve() 和 reject() 函數(shù)來實現(xiàn)的,可以在異步操作結(jié)束后調(diào)用這兩個函數(shù)改變 Promise 實例的狀態(tài),
1:先手寫一個簡單的Promise構(gòu)造函數(shù),從手寫的過程可以大致理解Promise的原理
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise (fn) {
var self = this;// 保存初始化狀態(tài)
this.state = PENDING;// 初始化狀態(tài)
this.value = null;// 用于保存 resolve 或者 rejected 傳入的值
this.resolvedCallbacks = [];// 用于保存 resolve 的回調(diào)函數(shù)
this.rejectedCallbacks = [];// 用于保存 reject 的回調(diào)函數(shù)
// 狀態(tài)轉(zhuǎn)變?yōu)?resolved 方法
function resolve (value) {
// 判斷傳入元素是否為 Promise 值,如果是,則狀態(tài)改變必須等待前一個狀態(tài)改變后再進行改變
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
// 保證代碼的執(zhí)行順序為本輪事件循環(huán)的末尾
setTimeout(() => {
// 只有狀態(tài)為 pending 時才能轉(zhuǎn)變,
if (self.state === PENDING) {
self.state = RESOLVED; // 修改狀態(tài)
self.value = value; // 設(shè)置傳入的值
// 執(zhí)行回調(diào)函數(shù)
self.resolvedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
function reject (value) {
setTimeout(() => {
if (self.state === PENDING) {
self.state = REJECTED;
self.value = value;
self.rejectedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 將兩個方法傳入函數(shù)執(zhí)行
try {
fn(resolve, reject);
} catch (e) {
// 遇到錯誤時,捕獲錯誤,執(zhí)行 reject 函數(shù)
reject(e);
}
}
MyPromise.prototype.then = function (onResolved, onRejected) {
// 首先判斷兩個參數(shù)是否為函數(shù)類型,因為這兩個參數(shù)是可選參數(shù)
onResolved =
typeof onResolved === "function"
? onResolved
: function (value) {
return value;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function (error) {
throw error;
};
// 如果是等待狀態(tài),則將函數(shù)加入對應列表中
if (this.state === PENDING) {
this.resolvedCallbacks.push(onResolved);
this.rejectedCallbacks.push(onRejected);
}
// 如果狀態(tài)已經(jīng)凝固,則直接執(zhí)行對應狀態(tài)的函數(shù)
if (this.state === RESOLVED) {
onResolved(this.value);
}
if (this.state === REJECTED) {
onRejected(this.value);
}
};
看上去有點復雜,時間有限,所以我決定放棄這個promise的手寫和原理,將各種用法弄清楚。
2:promise使用示例:
構(gòu)造函數(shù)Promise接收一個函數(shù)作為參數(shù),這個函數(shù)帶有兩個參數(shù),一個是resolve,一個是reject,這個函數(shù)內(nèi)部的代碼本身是同步的立即執(zhí)行函數(shù), 只有執(zhí)行resolve或者reject的時候是異步操作, 實際會先執(zhí)行對應的then/catch等,將then/catch里的代碼放進微任務(wù)隊列中,當主棧完成后,才會去調(diào)用resolve/reject中存放的方法執(zhí)行。
附:宏任務(wù)微任務(wù)相關(guān)js事件循環(huán)機制見另一篇博:js的事件循環(huán)機制
// Promise構(gòu)造函數(shù)接受一個函數(shù)(執(zhí)行器函數(shù))作為參數(shù),
// 該函數(shù)的兩個參數(shù)分別是resolve和reject。它們是兩個函數(shù),
// 由 JavaScript 引擎提供,不用自己部署。
const promise = new Promise(function (resolve, reject) {
// ... some code
if (/* 異步操作成功 */) {
// 在異步操作成功時調(diào)用,并將異步操作的結(jié)果,作為參數(shù)value傳遞出去;
resolve(value);
} else {
// 在異步操作失敗時調(diào)用,并將異步操作報出的錯誤,作為參數(shù)error/reason傳遞出去。
reject(error);
}
});
Promise實例的then方法接收兩個回調(diào)函數(shù)作為參數(shù),第一個回調(diào)當resolve()時執(zhí)行,是成功回調(diào),第二個回調(diào)reject()時執(zhí)行,是失敗回調(diào)。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
但同時Promise實例的catch方法也可以捕獲異常,這時候我有點疑惑,既然then方法已經(jīng)可以指出失敗回調(diào),那catch方法的意義是什么?先查了一下區(qū)別
then的失敗回調(diào)
僅能捕獲當前Promise鏈中前一個Promise的reject狀態(tài)或拋出的錯誤
catch方法
能捕獲整個Promise鏈中任意位置的未處理錯誤(包括then中拋出的異常)
這個解釋明確的說明catch在promise的鏈式調(diào)用中更好用,可以將錯誤處理集中在鏈式調(diào)用的末尾。這時候我又產(chǎn)生了兩個疑惑,第一個是鏈式調(diào)用是什么,第二個是如果集中處理了,那catch如何直到捕獲的錯誤是哪一步的錯誤
然后針對第一個問題,我開始學習promise的鏈式調(diào)用
3:promise的鏈式調(diào)用
let p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve("成功!"), 1000);
});
p1.then(result => {
console.log(result); // 輸出:成功!
return "下一步"; // 返回一個值,可以被下一個.then()捕獲
}).then(nextStep => {
console.log(nextStep); // 輸出:下一步
return new Promise((resolve, reject) => {
setTimeout(() => resolve("最終結(jié)果"), 500);
});
}).then(finalResult => {
console.log(finalResult); // 輸出:最終結(jié)果
});
首先,我已知promise的then方法返回的是一個新的promise,從這段代碼可以看出,p1的then方法的成功回調(diào)中返回一個字符串—‘下一步’,直接被下一個then方法捕獲了。換種說法就是,首先,第一個then函數(shù)本身返回了一個新的promise-------------
當你then里面的回調(diào)函數(shù)返回一個非promise的值的時候,這個新的promise會立即resolve該值,也就是直接執(zhí)行第二個then方法的第一個回調(diào)。
當你then里面的回調(diào)函數(shù)返回一個promise時候,then返回新的promise會跟隨你在回調(diào)函數(shù)中返回的promise的狀態(tài)[你也可以看成同一個promise,沒有差別,反正都是執(zhí)行相同的回調(diào)]。
略繞,總之這樣鏈式調(diào)用最大好處就是整個鏈條能保持清晰的執(zhí)行順序和統(tǒng)一的錯誤處理
4:鏈式調(diào)用的catch回調(diào)
【1】.catch()會捕獲第一個錯誤(無論是reject的錯誤還是throw 的異常),后續(xù)錯誤會被靜默忽略。
【2】如果同時存在then的錯誤回調(diào)和獨立的catch方法,遵循就近原則和互斥執(zhí)行【只執(zhí)行最近那個】
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject("失敗"), 1000);
});
promise.then(result => {
return "下一步";
},(error)=>{
return 'error' // 只執(zhí)行這個
}).then(nextStep => {
console.log(nextStep);
return new Promise((resolve, reject) => {
setTimeout(() => reject("第二步報錯"), 500);
});
}).then(finalResult => {
console.log(finalResult);
}).catch(error=>{
console.log(error)
});
5:promise的其他方法
all()
使用場景:并行請求多個接口后統(tǒng)一處理數(shù)據(jù),批量文件上傳全部完成后觸發(fā)通知
// 模擬三個異步API請求
const fetchUser = () =>
new Promise(resolve => setTimeout(() => resolve({id: 1, name: 'Alice'}), 800));
const fetchOrders = () =>
new Promise(resolve => setTimeout(() => resolve([101, 102, 103]), 500));
const fetchProducts = () =>
new Promise(resolve => setTimeout(() => resolve(['Laptop', 'Phone']), 300));
// 使用Promise.all并行執(zhí)行
Promise.all([fetchUser(), fetchOrders(), fetchProducts()])
.then(([user, orders, products]) => {
console.log('整合數(shù)據(jù):', { user, orders, products });
})
.catch(err => console.error('請求失敗:', err));
race()
采用競速模式,返回第一個敲定狀態(tài)(無論成功/失?。┑腜romise結(jié)果
使用場景:請求超時控制:將目標請求與setTimeout的reject Promise競速。【現(xiàn)在axios等工具直接傳入timeout參數(shù)即可】
// 模擬API請求(2秒返回)
const apiRequest = new Promise(resolve =>
setTimeout(() => resolve("數(shù)據(jù)獲取成功"), 2000)
);
// 設(shè)置1秒超時
const timeout = new Promise((_, reject) =>
setTimeout(() => reject("請求超時"), 1000)
);
// 競速執(zhí)行
Promise.race([apiRequest, timeout])
.then(res => console.log(res))
.catch(err => console.error(err));
finally()
使用場景:隱藏加載動畫無論請求成功與否
fetchData()
.then(res => console.log(res))
.catch(err => console.error(err.message))
.finally(() => {
console.log("無論成功失敗,都會執(zhí)行清理工作");
document.getElementById('loading').style.display = 'none';
});
6:附加一個執(zhí)行順序判斷的示例,測試下對promise的了解程度
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 輸出順序: script start->promise1->promise1 end->script end->promise2->settimeout
五、Async/Await
async 將一個函數(shù)標記為異步函數(shù),await 需要在異步函數(shù)中使用,標記當前操作是異步操作。async + await 必須配合 promise 使用,同時 async 和 await 必須一起使用。即 await 必須在 async 標記的函數(shù)中使用。
以下是一個使用示例:
function getProfile() {
return new Promise((resolve, reject) => {
// 使用定時器模擬接口請求
setTimeout(() => {
resolve({
code: 200,
msg: "用戶信息",
data: {
id: 1,
name: "liang"
}
})
}, 3000);
});
}
// 以下代碼會執(zhí)行 先輸出 123 再執(zhí)行輸出 res
function loadData() {
getProfile().then(res => {
console.log(res);
})
console.log(123);
}
// 下面寫法會使 getProfile() 先執(zhí)行
// 等待三秒后執(zhí)行完再把得到的結(jié)果賦值給左邊的res,然后再繼續(xù)往下執(zhí)行
async function loadData() {
const res = await getProfile()
console.log(res);
console.log(123);
}
我的理解:async創(chuàng)建了一個異步環(huán)境,在這個環(huán)境中其實是不存在同步代碼的。
首先在使用時每個await后面理論上都需要緊跟一個promise,例如:
async function asyncChain () {
try {
const stage1 = await new Promise(resolve => setTimeout(() => resolve(1), 1000));
console.log("第一階段結(jié)果:", stage1);
} catch (error) {
console.error("Async鏈錯誤:", error);
}
}
如果跟的是一個非 Promise 值時(如字符串、數(shù)字等),引擎會通過 Promise.resolve() 將其隱式轉(zhuǎn)換為已解決的 Promise 對象,此時代碼會立即繼續(xù)執(zhí)行,也就是跟了一個立即resolve那個字符串或者數(shù)字的promise。
例如:
async function demo() {
const result = await 42; // 等同于 await Promise.resolve(42)
console.log(result); // 直接輸出42
}
而await后面的代碼,比如上方代碼塊中的console.log(result),實際上相當于最近的前一個await后面緊跟的Promise的then回調(diào)里的內(nèi)容。
我:也就是await會阻塞async函數(shù)中在await之后的代碼,那是不是相當于await之后的代碼其實是當前await后面的這個promise實例的then回調(diào)?
deepseek:是的,await 之后的代碼可以視為當前 await 后面的 Promise 實例的 then 回調(diào),但兩者在實現(xiàn)機制和細節(jié)上存在差異。
async function demo() {
const result = await somePromise; // 阻塞后續(xù)代碼
console.log(result); // 相當于 somePromise.then(res => console.log(res))
}
六、Generator/yield
這個我寫了五年前端代碼沒有用到過。當然我是菜鳥哈哈。面試遇到就放棄好了。
到此這篇關(guān)于JavaScript中異步編程的實現(xiàn)方式的文章就介紹到這了,更多相關(guān)js異步編程實現(xiàn)方式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript中將其他類型的值轉(zhuǎn)換為字符串的方法及規(guī)則詳解
在JavaScript中,將其他類型的值轉(zhuǎn)換為字符串是一個非常常見且重要的操作,理解這些轉(zhuǎn)換規(guī)則可以幫助我們避免類型錯誤、寫出更健壯的代碼,本文將系統(tǒng)講解各種基本類型如何轉(zhuǎn)換為字符串,需要的朋友可以參考下2025-07-07
微信小程序使用webview頁面轉(zhuǎn)pdf文件代碼示例
工作需求,將webview的內(nèi)容導出到pdf輸出,下面這篇文章主要給大家介紹了關(guān)于微信小程序使用webview頁面轉(zhuǎn)pdf文件的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-09-09
淺談Javascript中的函數(shù)、this以及原型
下面小編就為大家?guī)硪黄獪\談Javascript中的函數(shù)、this以及原型。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10
Array, Array Constructor, for in loop, typeof, instanceOf
雖然在 JavaScript 中數(shù)組是是對象,但是沒有好的理由去使用 `for in` 循環(huán) 遍歷數(shù)組。相反,有一些好的理由不去使用 for in 遍歷數(shù)組。2011-09-09

