欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

js異步編程的演變:回調(diào)函數(shù)、Promise、?async/await?(代碼原理演示)

 更新時(shí)間:2025年10月03日 14:49:58   作者:wx66ece9f42611c  
文章講解了前端js異步編程的演變過程,從?回調(diào)函數(shù)到Promise鏈?zhǔn)秸{(diào)用,再到async/await的出現(xiàn),強(qiáng)調(diào)async/await核心優(yōu)勢:代碼像同步寫法、錯(cuò)誤處理簡單、非阻塞主線程,并指出其本質(zhì)是Generator+自動(dòng)執(zhí)行器的封裝,同時(shí)提醒避免串行await和合理使用try/catch范圍

前端開發(fā)里的異步場景:比如先請求“用戶信息”,再用用戶ID請求“訂單列表”,最后用訂單ID請求“訂單詳情”,同時(shí)不能讓頁面卡住(比如按鈕點(diǎn)不動(dòng)、滾動(dòng)不流暢)。而 async/await 就是解決這種場景的“最優(yōu)解”——讓異步代碼寫起來像“等快遞”一樣順理成章,卻不會(huì)阻塞主線程。

一、異步編程的“進(jìn)化史”:從“亂糟糟”到“清爽”

在async/await出現(xiàn)前,前端工程師為了處理異步任務(wù),踩過不少坑。我們以“按順序請求三個(gè)API”為例,看看每一代方案的問題。

1. 第一代:回調(diào)地獄(Callback Hell)——嵌套到“頭皮發(fā)麻”

最原始的異步處理靠回調(diào)函數(shù),比如用setTimeout模擬API請求:

// 模擬API請求:根據(jù)參數(shù)返回?cái)?shù)據(jù),1秒后執(zhí)行回調(diào)
function requestData(url, callback) {
  setTimeout(() => {
    // 模擬返回?cái)?shù)據(jù)(比如用戶信息、訂單等)
    const data = `來自${url}的數(shù)據(jù)`;
    callback(null, data); // 成功回調(diào):err為null,data為結(jié)果
  }, 1000);
}

// 需求:先請求用戶信息,再請求訂單,最后請求詳情
requestData("/api/user", (err, userData) => {
  if (err) return console.error("用戶請求失敗", err);
  console.log("拿到用戶數(shù)據(jù):", userData);
  
  // 用用戶數(shù)據(jù)里的userId請求訂單
  requestData(`/api/orders?userId=${userData.id}`, (err, orderData) => {
    if (err) return console.error("訂單請求失敗", err);
    console.log("拿到訂單數(shù)據(jù):", orderData);
    
    // 用訂單id請求詳情
    requestData(`/api/orderDetail?orderId=${orderData.id}`, (err, detailData) => {
      if (err) return console.error("詳情請求失敗", err);
      console.log("拿到詳情數(shù)據(jù):", detailData);
    });
  });
});

問題顯而易見:

  • 嵌套金字塔:每多一個(gè)異步任務(wù),就多一層嵌套,代碼像“千層餅”,后期維護(hù)時(shí)找bug要“逐層扒”;
  • 錯(cuò)誤處理麻煩:每個(gè)回調(diào)里都要寫if(err),一旦漏寫就可能導(dǎo)致報(bào)錯(cuò)無法捕獲;
  • 邏輯分散:“先做A、再做B、最后做C”的邏輯被拆在不同回調(diào)里,閱讀時(shí)要“跳著看”。

2. 第二代:Promise 鏈?zhǔn)秸{(diào)用——“扁平”但仍需“手動(dòng)銜接”

為了解決回調(diào)地獄,Promise應(yīng)運(yùn)而生:它把異步任務(wù)包裝成一個(gè)“容器”,用.then()鏈?zhǔn)秸{(diào)用,讓代碼變扁平:

// 用Promise重寫請求函數(shù):不再需要回調(diào),直接返回Promise
function requestDataPromise(url) {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = `來自${url}的數(shù)據(jù)`;
      resolve(data); // 異步成功后,用resolve返回結(jié)果
    }, 1000);
  });
}

// 鏈?zhǔn)秸{(diào)用:按順序請求
requestDataPromise("/api/user")
  .then((userData) => {
    console.log("拿到用戶數(shù)據(jù):", userData);
    // 返回下一個(gè)Promise,銜接下一個(gè).then
    return requestDataPromise(`/api/orders?userId=${userData.id}`);
  })
  .then((orderData) => {
    console.log("拿到訂單數(shù)據(jù):", orderData);
    return requestDataPromise(`/api/orderDetail?orderId=${orderData.id}`);
  })
  .then((detailData) => {
    console.log("拿到詳情數(shù)據(jù):", detailData);
  })
  .catch((err) => {
    // 集中捕獲所有環(huán)節(jié)的錯(cuò)誤,不用每個(gè)步驟寫if(err)
    console.error("某個(gè)請求失敗:", err);
  });

進(jìn)步很大,但仍有瑕疵:

  • 雖然扁平了,但需要頻繁寫.then(),代碼里穿插大量“銜接符”,不夠直觀;
  • 邏輯順序還是“鏈?zhǔn)降?rdquo;,如果任務(wù)多,.then()鏈條會(huì)很長,閱讀時(shí)要“順著鏈條找”。

3. 第三代:async/await——“像寫同步代碼一樣寫異步”

終于到了async/await出場的時(shí)候。它是Promise的“語法糖”,但把異步代碼的可讀性拉到了頂峰:

// 用async/await重寫:邏輯順序和代碼順序完全一致
async function fetchAllData() {
  try {
    // 1. 先請求用戶數(shù)據(jù):await“等待”Promise完成,再往下走
    const userData = await requestDataPromise("/api/user");
    console.log("拿到用戶數(shù)據(jù):", userData);
    
    // 2. 用用戶數(shù)據(jù)請求訂單:自然銜接,像同步代碼一樣
    const orderData = await requestDataPromise(`/api/orders?userId=${userData.id}`);
    console.log("拿到訂單數(shù)據(jù):", orderData);
    
    // 3. 用訂單數(shù)據(jù)請求詳情
    const detailData = await requestDataPromise(`/api/orderDetail?orderId=${orderData.id}`);
    console.log("拿到詳情數(shù)據(jù):", detailData);
    
    return "所有數(shù)據(jù)請求完成!";
  } catch (err) {
    // 集中捕獲所有await環(huán)節(jié)的錯(cuò)誤,和同步代碼的try/catch完全一致
    console.error("請求失敗:", err);
  }
}

// 調(diào)用async函數(shù)
console.log("程序開始執(zhí)行");
const result = fetchAllData();
console.log("等待數(shù)據(jù)請求..."); // 這行會(huì)先執(zhí)行,證明沒有阻塞
result.then((msg) => console.log(msg));

輸出順序(關(guān)鍵!看“非阻塞”證明):

程序開始執(zhí)行
等待數(shù)據(jù)請求...
// 1秒后(第一個(gè)請求完成)
拿到用戶數(shù)據(jù):來自/api/user的數(shù)據(jù)
// 又1秒后(第二個(gè)請求完成)
拿到訂單數(shù)據(jù):來自/api/orders?userId=xxx的數(shù)據(jù)
// 再1秒后(第三個(gè)請求完成)
拿到詳情數(shù)據(jù):來自/api/orderDetail?orderId=xxx的數(shù)據(jù)
所有數(shù)據(jù)請求完成!

核心優(yōu)勢直接“封神”:

  • 代碼即邏輯:“先做A,再做B,最后做C”的邏輯,和同步代碼寫法完全一致,不用跳著看;
  • 錯(cuò)誤處理簡單:用try/catch包裹所有異步步驟,和處理同步錯(cuò)誤的方式一樣;
  • 完全不阻塞:等待異步任務(wù)時(shí),主線程會(huì)去執(zhí)行其他代碼(比如上面的“等待數(shù)據(jù)請求...”),頁面不會(huì)卡住。

二、async/await 到底是“怎么干活”的?本質(zhì)是 Generator 的“自動(dòng)版”

很多人只知道async/await好用,卻不知道它背后的“黑魔法”——其實(shí)它是 Generator函數(shù) + Promise自動(dòng)執(zhí)行器 的封裝。我們一步步拆解開看。

1. 先認(rèn)識“半成品”:Generator函數(shù)

Generator函數(shù)是ES6引入的一種“可暫停、可恢復(fù)”的函數(shù),用function*定義,內(nèi)部用yield關(guān)鍵字暫停執(zhí)行:

// Generator函數(shù):處理三個(gè)異步請求
function* fetchGenerator() {
  // yield會(huì)暫停函數(shù),返回后面的Promise;恢復(fù)時(shí),把Promise的結(jié)果賦值給userData
  const userData = yield requestDataPromise("/api/user");
  console.log("拿到用戶數(shù)據(jù):", userData);
  
  const orderData = yield requestDataPromise(`/api/orders?userId=${userData.id}`);
  console.log("拿到訂單數(shù)據(jù):", orderData);
  
  const detailData = yield requestDataPromise(`/api/orderDetail?orderId=${orderData.id}`);
  console.log("拿到詳情數(shù)據(jù):", detailData);
  
  return "完成";
}

但Generator有個(gè)“缺點(diǎn)”:需要手動(dòng)“驅(qū)動(dòng)”它執(zhí)行

Generator函數(shù)調(diào)用后不會(huì)直接執(zhí)行,而是返回一個(gè)“迭代器(iterator)”,需要調(diào)用iterator.next()才能讓函數(shù)繼續(xù)執(zhí)行:

// 手動(dòng)驅(qū)動(dòng)Generator執(zhí)行
const iterator = fetchGenerator();

// 第一次調(diào)用next():函數(shù)執(zhí)行到第一個(gè)yield,返回{ value: Promise, done: false }
iterator.next().value.then((userData) => {
  // 第二次調(diào)用next(userData):把userData傳給第一個(gè)yield的左邊,函數(shù)執(zhí)行到第二個(gè)yield
  iterator.next(userData).value.then((orderData) => {
    // 第三次調(diào)用next(orderData):函數(shù)執(zhí)行到第三個(gè)yield
    iterator.next(orderData).value.then((detailData) => {
      // 第四次調(diào)用next(detailData):函數(shù)執(zhí)行完,done變?yōu)閠rue
      iterator.next(detailData);
    });
  });
});

你看,Generator已經(jīng)實(shí)現(xiàn)了“暫停異步任務(wù)、按順序執(zhí)行”,但需要手動(dòng)寫嵌套的.then()來驅(qū)動(dòng)——這顯然不夠方便。

2. 給Generator加個(gè)“自動(dòng)檔”:寫一個(gè)簡單的自動(dòng)執(zhí)行器

既然手動(dòng)驅(qū)動(dòng)太麻煩,我們可以寫一個(gè)函數(shù),自動(dòng)幫我們調(diào)用next(),直到Generator執(zhí)行完成:

// Generator自動(dòng)執(zhí)行器:接收Generator函數(shù),自動(dòng)驅(qū)動(dòng)它完成
function runGenerator(generatorFunc) {
  // 1. 創(chuàng)建迭代器
  const iterator = generatorFunc();
  
  // 2. 遞歸調(diào)用next()
  function autoNext(value) {
    // 執(zhí)行next(),拿到{ value: Promise, done: 布爾值 }
    const result = iterator.next(value);
    
    // 如果執(zhí)行完了,就退出
    if (result.done) return;
    
    // 如果沒執(zhí)行完,等待Promise完成后,遞歸調(diào)用autoNext
    result.value.then((data) => {
      autoNext(data); // 把Promise的結(jié)果傳給下一個(gè)next()
    }).catch((err) => {
      iterator.throw(err); // 捕獲錯(cuò)誤,傳給Generator的try/catch
    });
  }
  
  // 啟動(dòng)自動(dòng)執(zhí)行
  autoNext();
}

// 現(xiàn)在,調(diào)用自動(dòng)執(zhí)行器就夠了,不用手動(dòng)寫嵌套!
runGenerator(fetchGenerator);

這下Generator終于“自動(dòng)化”了——而 async/await 本質(zhì)上就是把“Generator函數(shù) + 自動(dòng)執(zhí)行器”封裝成了更簡潔的語法。

3. async/await 是怎么“封裝”的?

我們可以把async function理解為“自帶自動(dòng)執(zhí)行器的Generator函數(shù)”,瀏覽器或Node.js內(nèi)部幫我們做了這些事:

  • async關(guān)鍵字:告訴引擎“這是一個(gè)需要自動(dòng)執(zhí)行的異步函數(shù)”,調(diào)用時(shí)會(huì)自動(dòng)創(chuàng)建迭代器并驅(qū)動(dòng)執(zhí)行;
  • await關(guān)鍵字:相當(dāng)于yield的“語法糖”,自動(dòng)等待后面的Promise完成,并把結(jié)果返回,不用手動(dòng)處理next();
  • 錯(cuò)誤處理:內(nèi)部自動(dòng)捕獲Promise的reject狀態(tài),拋給外層的try/catch,不用手動(dòng)寫iterator.throw()。

簡單來說:async/await = Generator函數(shù) + 內(nèi)置自動(dòng)執(zhí)行器 + 更友好的語法。

三、關(guān)鍵誤區(qū):async/await 不是“同步”,而是“偽同步”

很多人用多了async/await,會(huì)誤以為它是“同步代碼”——但實(shí)際上它只是“看起來同步”,本質(zhì)還是異步,不會(huì)阻塞主線程。我們用一個(gè)“事件循環(huán)”的例子來證明:

console.log("1. 全局同步代碼開始");

// async函數(shù)
async function asyncDemo() {
  console.log("2. async函數(shù)內(nèi)部同步代碼");
  // await后面的Promise會(huì)暫停函數(shù),把后續(xù)代碼放進(jìn)“微任務(wù)隊(duì)列”
  await Promise.resolve("模擬異步完成"); 
  console.log("5. await之后的代碼(微任務(wù))");
}

// 調(diào)用async函數(shù)
asyncDemo();

// 其他同步代碼
console.log("3. 全局同步代碼繼續(xù)");
setTimeout(() => {
  console.log("6. setTimeout回調(diào)(宏任務(wù))");
}, 0);
console.log("4. 全局同步代碼結(jié)束");

最終輸出順序:

1. 全局同步代碼開始
2. async函數(shù)內(nèi)部同步代碼
3. 全局同步代碼繼續(xù)
4. 全局同步代碼結(jié)束
5. await之后的代碼(微任務(wù))
6. setTimeout回調(diào)(宏任務(wù))

為什么會(huì)這樣?拆解執(zhí)行流程:

  1. 主線程先執(zhí)行所有同步代碼:1→2→3→4;
  2. 遇到await Promise.resolve()時(shí),引擎會(huì):
  • 暫停asyncDemo函數(shù),把await后面的代碼(console.log("5..."))放進(jìn)“微任務(wù)隊(duì)列”;
  • 主線程繼續(xù)執(zhí)行其他同步代碼(3→4);
  1. 同步代碼執(zhí)行完后,主線程會(huì)清空“微任務(wù)隊(duì)列”,執(zhí)行console.log("5...");
  2. 微任務(wù)執(zhí)行完后,再執(zhí)行“宏任務(wù)隊(duì)列”里的setTimeout回調(diào)(6)。

這就證明了:await不會(huì)阻塞主線程,它只是讓函數(shù)內(nèi)部的代碼“按順序等”,主線程該干嘛干嘛——這就是“偽同步”的本質(zhì)。

四、實(shí)際開發(fā)中的“避坑指南”

async/await雖好,但新手容易踩坑,分享兩個(gè)高頻注意點(diǎn):

1. 并行任務(wù)別用“串行await”,用Promise.all

如果多個(gè)異步任務(wù)之間沒有依賴(比如同時(shí)請求“商品列表”和“用戶信息”),不要用多個(gè)await串行執(zhí)行,會(huì)浪費(fèi)時(shí)間:

// 錯(cuò)誤寫法:串行執(zhí)行,總耗時(shí)=1秒+1秒=2秒
async function badFetch() {
  const goods = await requestDataPromise("/api/goods"); // 1秒
  const user = await requestDataPromise("/api/user"); // 又1秒
  console.log(goods, user);
}

// 正確寫法:并行執(zhí)行,總耗時(shí)=1秒(兩個(gè)請求同時(shí)發(fā))
async function goodFetch() {
  // 用Promise.all同時(shí)發(fā)起多個(gè)請求,await等待所有請求完成
  const [goods, user] = await Promise.all([
    requestDataPromise("/api/goods"),
    requestDataPromise("/api/user")
  ]);
  console.log(goods, user);
}

2. try/catch的范圍要“合理”

如果希望某個(gè)異步任務(wù)失敗后,其他任務(wù)還能繼續(xù)執(zhí)行,不要把所有await都放進(jìn)一個(gè)try/catch

async function fetchWithError() {
  try {
    const user = await requestDataPromise("/api/user"); // 假設(shè)這個(gè)請求失敗
    console.log("用戶數(shù)據(jù):", user);
  } catch (err) {
    console.error("用戶請求失敗:", err); // 只捕獲用戶請求的錯(cuò)誤
  }
  
  // 即使上面失敗,這里仍會(huì)執(zhí)行
  const goods = await requestDataPromise("/api/goods");
  console.log("商品數(shù)據(jù):", goods);
}

五、總結(jié):async/await 的核心邏輯

1. 原理公式

async/await = Generator函數(shù)(暫停/恢復(fù)) + Promise自動(dòng)執(zhí)行器(驅(qū)動(dòng)流程) + 事件循環(huán)(調(diào)度微任務(wù))

2. 核心優(yōu)勢對比

特性

回調(diào)函數(shù)

Promise鏈?zhǔn)?/p>

async/await

代碼可讀性

差(嵌套)

中(鏈?zhǔn)剑?/p>

優(yōu)(同步寫法)

錯(cuò)誤處理

繁瑣(每層判斷)

中(.catch())

優(yōu)(try/catch)

是否阻塞主線程

學(xué)習(xí)成本

中(需理解原理)

并行任務(wù)處理

復(fù)雜(需手動(dòng)管理)

優(yōu)(Promise.all)

優(yōu)(配合Promise.all)

3. 面試怎么答?

“async/await是Promise的語法糖,本質(zhì)基于Generator函數(shù)和自動(dòng)執(zhí)行器:

  • async標(biāo)記函數(shù)為異步,調(diào)用時(shí)自動(dòng)創(chuàng)建迭代器并驅(qū)動(dòng)執(zhí)行;
  • await會(huì)暫停函數(shù),等待后面的Promise完成后,把結(jié)果返回并恢復(fù)函數(shù);
  • 它讓代碼看起來像同步,但實(shí)際是通過事件循環(huán)調(diào)度微任務(wù)實(shí)現(xiàn)異步,不會(huì)阻塞主線程。”

理解async/await,不只是會(huì)用,更要明白它是JavaScript異步編程“從復(fù)雜到簡潔”的必然結(jié)果——它解決了“按順序處理異步任務(wù)”的核心痛點(diǎn),同時(shí)保留了異步的高效性,這也是它能成為前端開發(fā)“標(biāo)配”的原因。

到此這篇關(guān)于js異步編程的演變:回調(diào)函數(shù)、Promise、‌async/await‌(代碼原理演示)的文章就介紹到這了,更多相關(guān)js異步:回調(diào)函數(shù)、Promise、‌async/await‌內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • js中for...in循環(huán)對象時(shí)輸出key值順序混亂問題解決

    js中for...in循環(huán)對象時(shí)輸出key值順序混亂問題解決

    很久之前就有前輩告訴我用for...in循環(huán)對象屬性的順序不是固定的,xiam?這篇文章主要給大家介紹了關(guān)于js中for...in循環(huán)對象時(shí)輸出key值順序混亂問題解決方法,需要的朋友可以參考下
    2023-11-11
  • javascript輸出AscII碼擴(kuò)展集中的字符方法

    javascript輸出AscII碼擴(kuò)展集中的字符方法

    下面小編就為大家?guī)硪黄猨avascript輸出AscII碼擴(kuò)展集中的字符方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,祝大家游戲愉快哦
    2016-12-12
  • 如何利用模板將HTML從JavaScript中抽離

    如何利用模板將HTML從JavaScript中抽離

    這篇文章主要為大家詳細(xì)介紹了利用模板將HTML從JavaScript中抽離的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • JavaScript設(shè)計(jì)模式之工廠方法模式介紹

    JavaScript設(shè)計(jì)模式之工廠方法模式介紹

    這篇文章主要介紹了JavaScript設(shè)計(jì)模式之工廠方法模式介紹,本文講解了簡單工廠模式、多個(gè)工廠方法模式等內(nèi)容,需要的朋友可以參考下
    2014-12-12
  • layui實(shí)現(xiàn)左側(cè)菜單點(diǎn)擊右側(cè)內(nèi)容區(qū)顯示

    layui實(shí)現(xiàn)左側(cè)菜單點(diǎn)擊右側(cè)內(nèi)容區(qū)顯示

    這篇文章主要為大家詳細(xì)介紹了layui實(shí)現(xiàn)左側(cè)菜單點(diǎn)擊右側(cè)內(nèi)容區(qū)顯示,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • js定義類的幾種方法(推薦)

    js定義類的幾種方法(推薦)

    下面小編就為大家?guī)硪黄猨s定義類的幾種方法(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-06-06
  • 前端如何控制并發(fā)請求舉例詳解

    前端如何控制并發(fā)請求舉例詳解

    在項(xiàng)目中我們會(huì)遇到一次請求多個(gè)接口,當(dāng)所有請求結(jié)束后進(jìn)行操作,也會(huì)遇到多個(gè)請求(大量)同時(shí)進(jìn)行請求資源,這篇文章主要給大家介紹了關(guān)于前端如何控制并發(fā)請求舉例的相關(guān)資料,需要的朋友可以參考下
    2024-09-09
  • 整理關(guān)于Bootstrap導(dǎo)航的慕課筆記

    整理關(guān)于Bootstrap導(dǎo)航的慕課筆記

    這篇文章主要為大家整理了關(guān)于Bootstrap導(dǎo)航的慕課筆記,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • Bootstrap字體圖標(biāo)無法正常顯示的解決方法

    Bootstrap字體圖標(biāo)無法正常顯示的解決方法

    這篇文章主要為大家詳細(xì)介紹了Bootstrap字體圖標(biāo)無法正常顯示的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • js+Html實(shí)現(xiàn)表格可編輯操作

    js+Html實(shí)現(xiàn)表格可編輯操作

    這篇文章主要為大家詳細(xì)介紹了js+Html實(shí)現(xiàn)表格可編輯操作,能動(dòng)態(tài)添加刪除行,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-04-04

最新評論