最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文帶你詳細拆解JavaScript中Promise的原理和真實應用

 更新時間:2026年03月30日 09:48:28   作者:森葉  
本文從中文語義出發(fā),逐層深入到 .then 源碼、resolve 與 then 的聯(lián)動機制、await 的編譯真相,最后用一道面試實戰(zhàn)題把所有知識串起來,讀完之后,Promise 對你來說不再是一個需要記憶的 API,而是一個可以用直覺推導的思維模型

我一直覺得 Promise 最大的理解障礙不是技術,而是命名。.then 這個詞太抽象了——“然后”?然后什么?然后就完了?但如果你把它翻譯成中文的**“等”**,一切就清晰了。

promise.then(fn)promise.等(fn)

等還沒有結果。等完了,然后呢?還需要繼續(xù)等嗎?

再看 Promise 這個詞本身——承諾

“我承諾幫你干這件事,事情被包在承諾里,你就等著,干完了叫你。”

一個"承諾",一個"等",就是 Promise 全部的秘密。

本文從中文語義出發(fā),逐層深入到 .then 源碼、resolve 與 then 的聯(lián)動機制、await 的編譯真相,最后用一道面試實戰(zhàn)題把所有知識串起來。讀完之后,Promise 對你來說不再是一個需要記憶的 API,而是一個可以用直覺推導的思維模型。

一、承諾:Promise 的中文本義

Promise 翻譯成中文就是承諾。而且這個詞和 Promise 的語義幾乎完美對應:

“我承諾給你一個結果,但不是現(xiàn)在。”

const 承諾 = fetch('/api');
// 我承諾會給你數(shù)據,但現(xiàn)在還沒拿到
// 你先拿著這個"承諾",等我兌現(xiàn)

Promise 的三種狀態(tài)用"承諾"來理解天然成立:

pending    → 承諾還沒兌現(xiàn),等著
fulfilled  → 承諾兌現(xiàn)了,拿到了結果
rejected   → 承諾違約了,出錯了

resolvereject 也可以直接翻譯:

new Promise((兌現(xiàn), 違約) => {
  if (成功) 兌現(xiàn)(結果);    // 信守承諾
  else 違約(錯誤);          // 承諾作廢
});

而整個 Promise 機制,就是三個角色的協(xié)作:

承諾人(executor):  我來干活
等待人(.then):     我等著,干完了叫我
結果(resolve 的值):干完的交付物

我承諾幫你干,你就等著,干完了叫你。 這就是 Promise 的全部。

二、用"等"重新理解.then鏈

先看一段最普通的 Promise 代碼:

fetch('/api')
  .then(data => parse(data))
  .then(result => save(result))
  .then(() => console.log('完成'));

現(xiàn)在把 .then 替換成"等":

發(fā)請求
  .等(數(shù)據回來了 → 解析)
  .等(解析完了 → 保存)
  .等(保存完了 → 打印"完成")

讀起來是不是像說人話了?每個"等"都在問同一個問題:

  • 等什么? → 等上一步完成
  • 等到了,拿到什么? → 上一步的結果
  • 等完之后呢? → 看你的回調返回什么,可能還要繼續(xù)等

最后一點最關鍵——等完了可能還要等。這就是 Promise 鏈能無限串下去的原因。

而鏈式調用就是承諾的傳遞

A 承諾:我?guī)湍隳脭?shù)據          → 你等
A 兌現(xiàn)了,你拿到數(shù)據
B 承諾:我?guī)湍憬馕鲞@個數(shù)據     → 你等
B 兌現(xiàn)了,你拿到解析結果
C 承諾:我?guī)湍愦娴綌?shù)據庫       → 你等
C 兌現(xiàn)了,完事

每一步干完活的人說"我搞定了",下一個人才開始干。活是一個一個承諾出去的,你就一個一個等。

三、"等"的三種結局

當"等"到了上一步的結果,你的回調函數(shù)執(zhí)行了,它的返回值決定了下一個"等"的命運:

結局一:返回一個普通值 → 等到了,立刻交付

promise.等(data => {
  return data + 1;   // 返回普通值 2
});
// 下一個"等"立刻拿到 2,不用真的等

結局二:返回一個新的 Promise → 還得繼續(xù)等

promise.等(data => {
  return fetch('/api2');   // 返回新的 Promise(新承諾)
});
// 下一個"等"被鎖住了,要等 fetch 完成才能繼續(xù)

結局三:不返回任何值 → 等到了個寂寞

promise.等(data => {
  console.log(data);   // 用了,但沒 return
});
// 下一個"等"拿到 undefined,值斷了

結局二是整個 Promise 機制最核心的特性。 正是因為"等完了還可以繼續(xù)等",才讓異步操作能像水管一樣串聯(lián)起來。

四、從源碼看"等"的實現(xiàn)

下面是一個簡化但忠實于 Promise/A+ 規(guī)范的實現(xiàn)。我在關鍵位置標注了"等"和"承諾"的語義:

class MyPromise {
  constructor(executor) {
    this.status = 'pending';     // 承諾還沒兌現(xiàn)
    this.value = undefined;       // 兌現(xiàn)后的結果
    this.callbacks = [];          // 排隊等的人

    const resolve = (value) => {
      if (this.status !== 'pending') return;
      this.status = 'fulfilled';  // 承諾兌現(xiàn)了!
      this.value = value;
      this.callbacks.forEach(cb => this._handle(cb));  // 通知所有排隊的人
    };

    executor(resolve);   // 把"兌現(xiàn)"的能力交給承諾人
  }

  then(onFulfilled) {
    // ★ 每次"等",都會產生一個新的"承諾"
    let resolve2;
    const 新承諾 = new MyPromise((resolve) => {
      resolve2 = resolve;     // 把新承諾的兌現(xiàn)開關拿出來,先不按
    });

    const callback = { onFulfilled, resolve2 };

    if (this.status === 'fulfilled') {
      this._handle(callback);         // 承諾已兌現(xiàn),直接處理
    } else {
      this.callbacks.push(callback);   // 還沒兌現(xiàn),留個電話等通知
    }

    return 新承諾;   // 返回的永遠是一個新的承諾,不是具體的值
  }

  _handle({ onFulfilled, resolve2 }) {
    queueMicrotask(() => {
      const result = onFulfilled(this.value);

      if (result instanceof MyPromise) {
        // ★ 等到的結果還是一個承諾 → 把自己的開關交出去
        result.then(resolve2);
      } else {
        // ★ 等到的是一個確切的值 → 直接按下開關
        resolve2(result);
      }
    });
  }
}

整個源碼的核心邏輯用"等"和"承諾"來概括就一句話:

承諾兌現(xiàn)了,看結果是不是還是一個承諾。是 → 繼續(xù)等;不是 → 交付。

五、resolve和.then是怎么聯(lián)動的

看完源碼結構,一個最關鍵的問題浮出水面:resolve 寫在 constructor 里,.then 寫在外面,它們互不知道對方什么時候執(zhí)行。那結果是怎么傳遞過去的?

答案是 this.callbacks 這個數(shù)組——它是 resolve.then 之間的橋梁。

        callbacks(共享的信箱)
              │
     ┌────────┴────────┐
     │                  │
  resolve              .then
  (承諾人)            (等待人)
     │                  │
  "我干完了,           "我來等結果,
   看看有沒有人等著"     看看是不是已經干完了"

誰先誰后?兩種情況都能處理。

情況一:先.then,后resolve(最常見)

const p = new Promise((resolve) => {
  setTimeout(() => resolve('hello'), 1000);
});
p.then(value => console.log(value));

時刻 0ms:
  .then 執(zhí)行,發(fā)現(xiàn) status 是 pending
  → 把 { onFulfilled, resolve2 } 存進 callbacks
  → 就像留了個電話號碼:"兌現(xiàn)了打這個號通知我"

時刻 1000ms:
  resolve('hello') 被調用
  → status 改為 fulfilled,value 存為 'hello'
  → 遍歷 callbacks,逐個調 _handle
  → onFulfilled('hello') 執(zhí)行

先留電話,活干完了打電話通知。

情況二:先resolve,后.then

const p = Promise.resolve('hello');
p.then(value => console.log(value));

.then 執(zhí)行,發(fā)現(xiàn) status 已經是 fulfilled
→ 不存 callbacks,直接調 _handle
→ onFulfilled('hello') 執(zhí)行

到了才發(fā)現(xiàn)活早干完了,結果就在柜臺上,直接拿走。

兩邊各自只關心自己的事,但合在一起恰好覆蓋了所有時序可能:

resolve 的邏輯:
"我干完了"
→ 改 status,存 value
→ callbacks 里有人嗎?有就逐個通知,沒有就算了(值存著,誰來都能拿)

.then 的邏輯:
"我來等"
→ status 是 fulfilled 嗎?
→ 是 → 直接拿值走人
→ 不是 → 把自己塞進 callbacks,等著被叫

這個設計還有一個精妙的約束——狀態(tài)不可逆

const resolve = (value) => {
  if (this.status !== 'pending') return;   // 兌現(xiàn)過了就不能再變
};
pending → fulfilled    ? 可以
fulfilled → pending    ? 不行

一旦承諾兌現(xiàn)就永遠是兌現(xiàn)的,結果永久緩存在 this.value 里。不管多少個 .then 來,拿到的都是同一個結果,不會過期。普通的發(fā)布-訂閱(EventEmitter)做不到這一點——事件觸發(fā)了你沒監(jiān)聽就錯過了。但承諾不會,承諾兌現(xiàn)了就是兌現(xiàn)了,什么時候來取都行。

六、result.then(resolve2)—— 把自己的命運交給別人

源碼中最精妙的一行:

_handle({ onFulfilled, resolve2 }) {
  queueMicrotask(() => {
    const result = onFulfilled(this.value);

    if (result instanceof MyPromise) {
      result.then(resolve2);       // 這一行
    } else {
      resolve2(result);
    }
  });
}

resolve2 是誰的?是"新承諾"(.then 返回的那個 Promise)的兌現(xiàn)開關。正常情況下,這個開關應該由 _handle 自己按下。但當 result 是一個 Promise 時:

result.then(resolve2);

翻譯成中文:

嘿 result,我不按這個開關了。
你什么時候兌現(xiàn)了承諾,你幫我按。
你兌現(xiàn)了什么值,那就是我的值。

這就是控制權轉移——新承諾把自己的命運鎖定到了 result 身上。

對比兩個分支:

resolve2(result);          // 自己按開關:我等到了確切的值,直接交付
result.then(resolve2);     // 把開關交給別人:我等到的還是一個承諾,讓它來決定我的命運

用生活場景打比方:

你去餐廳點了菜(發(fā)起 .then)
服務員給你一個取餐號(返回新的 Promise)

情況一:菜做好了,直接上桌
  → resolve2(菜),你吃上了

情況二:服務員說"這道菜的食材要等隔壁店送來"
  → 服務員把你的取餐號轉給了隔壁店
  → 隔壁店送到了 → 你的號才被叫到
  → result.then(resolve2)

七、new Promise()vs.then()—— 自己承諾 vs 被安排的承諾

// 自己承諾:你完全控制什么時候兌現(xiàn)
const a = new Promise((resolve) => {
  setTimeout(() => resolve('hello'), 1000);
  // 你自己決定 1 秒后兌現(xiàn)承諾
});

// 被安排的承諾:你控制不了
const b = a.then(value => {
  return value + ' world';
});
// b 什么時候兌現(xiàn),取決于 a 什么時候兌現(xiàn)
// 以及你的回調返回的是值還是新的承諾

而且 .then() 返回的永遠是一個新的承諾,不是具體的值:

const a = Promise.resolve(1);

const b = a.then(v => v + 1);     // b 是承諾,不是 2
const c = a.then(v => 'hello');   // c 是承諾,不是 'hello'
const d = a.then(v => undefined); // d 是承諾,不是 undefined

因為 .then 的第一件事就是 new 一個新的 Promise 然后 return 它。你的回調返回值只決定了這個承諾以什么值兌現(xiàn),而不是替代承諾本身。

這也是 .then 能無限鏈下去的原因——每一步返回的都是承諾,承諾就有 .等 方法。如果返回的是 2,那 2.等(fn) 就報錯了,鏈直接斷了。

八、等到了個寂寞:不返回值的陷阱

大多數(shù)人這樣寫 .then

fetch('/api').then(data => {
  console.log(data);    // 用了,但沒 return
});

回到源碼,沒有 return 意味著 result = undefined

_handle({ onFulfilled, resolve2 }) {
    const result = onFulfilled(this.value);
    //    ↑ 沒有 return,result 是 undefined

    if (result instanceof MyPromise) {
      result.then(resolve2);       // 走不到這里
    } else {
      resolve2(result);            // resolve2(undefined)
    }
}

下一個"等"等到了 undefined——承諾倒是兌現(xiàn)了,但兌現(xiàn)了個寂寞。

fetch('/api')
  .等(data => {
    console.log(data);   // 有值
    // 沒有 return
  })
  .等(result => {
    console.log(result); // undefined,值斷了
  });

很多人覺得沒問題,因為后面沒人接了。但這說明他們把 .then 當成了事件監(jiān)聽器,而不是管道變換器——上一節(jié)的輸出應該是下一節(jié)的輸入。

在實際代碼里,這個細節(jié)也很致命:

chain.then(() => promise)     // ? 箭頭函數(shù)隱式 return promise
chain.then(() => { promise }) // ? 花括號,沒 return,等了個寂寞,鏈直接穿透

只差一對花括號,一個是"等到了還要繼續(xù)等",一個是"承諾兌現(xiàn)了個空氣"。

九、await—— 讓你假裝不用等的語法糖

幻覺

async function foo() {
  const value = await somePromise;
  console.log(value);   // 看起來直接拿到了值
}

真相

function foo() {
  return somePromise.等(value => {
    console.log(value);   // 值還是在回調參數(shù)里
  });
}

await 做的事情就是讓編譯器幫你把函數(shù)劈開——遇到 await 就是一刀,后半部分整個塞進 .then 的回調里:

async function foo() {
  // -------- 第一半:同步執(zhí)行 --------
  console.log('開始');
  const value = await somePromise;
  // -------- 第二半:塞進 .then 回調 --------
  console.log(value);
  return value + 1;
}

// 編譯器翻譯后:
function foo() {
  console.log('開始');
  return somePromise.then(value => {
    console.log(value);
    return value + 1;
  });
}

多個 await 就是多次劈開:

async function foo() {
  const a = await p1;    // 第一刀
  const b = await p2;    // 第二刀
  return a + b;
}

// 等價于:
function foo() {
  return p1.等(a => {
    return p2.等(b => {
      return a + b;
    });
  });
}

值永遠在回調里,從來沒有"逃出"過

如果你追問:那外面怎么拿到 a + b 的?

const c = await foo();

展開:

foo().等(c => {
  // c 在回調參數(shù)里
});

再往外套一層?

async function outer() {
  const result = await main();
}
// 展開:
main().等(result => { ... });

一直追到調用棧最頂層:

main();   // 返回 Promise,沒人再 await 它了

值從來沒有被賦值給回調外部的任何變量。 它只是從一個"等"的回調參數(shù),傳到下一個"等"的回調參數(shù),一路傳下去。

所以 const c = await foo() 不是"賦值",是傳參。resolve(a + b) 把值傳給了 .then(c => ...) 的參數(shù) cawait 只是讓這個參數(shù)看起來像是賦值給了一個局部變量。

await 沒有發(fā)明任何新的取值方式,它只是讓你不用手寫嵌套的"等"了。你每次寫下 await,其實都是在說:“我先等等。” 只不過編譯器替你排好了隊,讓你以為自己沒在等而已。

十、實戰(zhàn):并行執(zhí)行,串行輸出

理解了"承諾"和"等",來看一道面試級別的問題:

實現(xiàn)一個隊列,任務 push 時立即執(zhí)行(并行),但結果按 push 順序輸出(串行)。

function createQueue(onResult) {
  let 等待鏈 = Promise.resolve();   // 初始:一個已經兌現(xiàn)的承諾

  return {
    push(task) {
      const promise = task();       // 立即執(zhí)行(并行)
      等待鏈 = 等待鏈
        .等(() => promise)            // 等上一個輸出完 → 返回當前任務的承諾 → 繼續(xù)等
        .等(onResult);                // 等任務兌現(xiàn) → 輸出結果
    },
    done() {
      return 等待鏈;
    }
  };
}

用"等"來讀這段代碼:

push(A):
  等待鏈(已兌現(xiàn))
    .等(→ promiseA)     // 不用等,直接執(zhí)行,但返回了 promiseA → 要等它兌現(xiàn)
    .等(onResult)       // 等 A 兌現(xiàn) → 輸出 A

push(B):
  等待鏈(現(xiàn)在是 A 的輸出承諾)
    .等(→ promiseB)     // A 還沒輸出完,這個回調還不執(zhí)行
    .等(onResult)       // 等 B 兌現(xiàn) → 輸出 B

push(C):
  等待鏈(現(xiàn)在是 B 的輸出承諾)
    .等(→ promiseC)     // B 還沒輸出完,繼續(xù)排隊等
    .等(onResult)       // 等 C 兌現(xiàn) → 輸出 C

關鍵洞察:任務在 push 時就已經開始執(zhí)行了(并行),但"等"鏈保證了結果按順序釋放。等鏈輪到某個任務時,它的承諾可能早就兌現(xiàn)了,那就直接通過,不浪費時間。

"等"不一定真的要花時間等。它只保證了順序,而沒有犧牲并行性。

就像你同時找了三個人幫忙,但跟他們說:

"你們仨同時干,但交活的時候排好隊,
 A 先交,B 再交,C 最后交,
 別管誰先干完,順序不能亂。"

干活并行,交活串行。承諾的"等"就是這個排隊交活的機制。

完整測試:

function delay(ms, value) {
  return () => new Promise(resolve => {
    console.log(`[啟動] ${value}`);
    setTimeout(() => resolve(value), ms);
  });
}

const results = [];
const queue = createQueue(result => {
  results.push(result);
  console.log(`[輸出] ${result}`);
});

queue.push(delay(300, 'A'));   // 最慢
queue.push(delay(200, 'B'));
queue.push(delay(100, 'C'));   // 最快

queue.done().then(() => {
  console.log(results);
  // [啟動] A
  // [啟動] B        ← 三個同時啟動
  // [啟動] C
  // [輸出] A        ← 但輸出嚴格按順序
  // [輸出] B
  // [輸出] C
});

C 最先完成,但它要 A 和 B 先輸出。A 最慢,但它排第一個,不用等任何人。

十一、更高的視角:類是維度的上升

回過頭來看 Promise 的設計——resolve 寫在 constructor 里,.then 寫在方法里,兩邊互不知道對方什么時候執(zhí)行。但它們通過 status + callbacks 這兩個共享狀態(tài),無論誰先誰后,結果都對

平時我們寫代碼,都是在單一時間線上思考:

// 我是發(fā)送方,我只管發(fā)
socket.send(data);

// 我是接收方,我只管收
socket.onmessage = (data) => { ... };

這兩段代碼各管各的,它們之間的協(xié)調要靠開發(fā)者自己在腦子里對齊時序。

但 Promise 做了什么?把兩條時間線折疊進一個對象里

const p = new Promise((resolve) => {
  // 時間線 A:發(fā)送/生產 —— 承諾人
  干活(結果 => resolve(結果));
});

p.then(結果 => {
  // 時間線 B:接收/消費 —— 等待人
});

兩條原本獨立的時間線,通過 new Promise() 這個"高維容器"被收納到了同一個實體里。它們在時間上可能相隔很久,但在邏輯上被綁定在了一起。

這個模式不只在 Promise 里,到處都是:

Promise:       封裝了"兌現(xiàn)承諾"和"等待承諾"兩條時間線
EventEmitter:  封裝了"發(fā)事件"和"收事件"兩條時間線
Redux Store:   封裝了"寫狀態(tài)"和"讀狀態(tài)"兩條時間線
數(shù)據庫事務:     封裝了"多個操作"和"成功/回滾"兩條時間線

每一個都是同樣的模式——把多個可能發(fā)生在不同時刻的邏輯,折疊進一個高維容器里統(tǒng)一管理。

所以類的封裝,不只是"把數(shù)據和方法放在一起"這么簡單。它真正的價值是維度的上升

將不同時間維度上的邏輯,收納進同一個空間維度的實體里,讓它們能夠協(xié)作。

Promise 管理的是"承諾與兌現(xiàn)"的關系,EventEmitter 管理的是"發(fā)布與訂閱"的關系,事務管理的是"操作與一致性"的關系。好的類設計讓人覺得"優(yōu)雅",正是因為它不是在解決當下這一刻的問題,而是在管理一段跨越時間的關系。

下次設計一個類的時候,可以問自己一個問題:

“我是在封裝數(shù)據,還是在折疊時間線?”

如果是后者,你大概率在做真正有價值的抽象。

十二、總結

概念用"承諾"和"等"來理解
new Promise(executor)我承諾幫你干這件事,executor 里就是要干的活
resolve(value)承諾兌現(xiàn)了,交付結果
reject(error)承諾違約了,告知原因
.then(fn)等承諾兌現(xiàn),然后執(zhí)行 fn
.then 的返回值永遠是一個新的承諾,不是具體的值
fn 返回普通值新承諾立刻兌現(xiàn)
fn 返回 Promise新承諾鎖定到返回的 Promise,繼續(xù)等
fn 不返回值新承諾兌現(xiàn)了個寂寞(undefined)
callbacks 數(shù)組resolve 和 .then 的聯(lián)動橋梁——先到的留電話,后到的查結果
狀態(tài)不可逆承諾兌現(xiàn)了就是兌現(xiàn)了,什么時候來取都行
await編譯器幫你把函數(shù)劈開,后半部分塞進 .then 的回調里
await 的值從來沒有"逃出"回調,只是從一個"等"傳到下一個"等"
并行執(zhí)行串行輸出活同時干,但承諾排著隊兌現(xiàn)
類的本質維度的上升——把多條時間線折疊進一個對象里

如果 JavaScript 是中國人發(fā)明的,Promise 一定叫"承諾",.then 一定叫 .等,resolve 一定叫"兌現(xiàn)",reject 一定叫"違約"。

而你每次寫下 await,其實都是在說:“我先等等。”

只不過編譯器替你排好了隊,讓你以為自己沒在等而已。

以上就是一文帶你詳細拆解JavaScript中Promise的原理和真實應用的詳細內容,更多關于JavaScript Promise原理與應用的資料請關注腳本之家其它相關文章!

相關文章

最新評論

大宁县| 湛江市| 锡林浩特市| 涡阳县| 华安县| 邵阳县| 渝北区| 德格县| 安岳县| 德江县| 瓮安县| 昌都县| 金阳县| 浏阳市| 贵州省| 德州市| 柳州市| 清苑县| 同德县| 定结县| 通城县| 三门峡市| 苍南县| 辽宁省| 五家渠市| 屏东县| 新泰市| 邯郸县| 靖宇县| 成都市| 铜山县| 绥宁县| 武陟县| 耒阳市| 苏尼特右旗| 伊宁县| 防城港市| 钟山县| 台中市| 渑池县| 南川市|