JavaScript Promise:簡介

Promise 簡化延遲和非同步運算。承諾代表尚未完成的作業。

阿奇巴德 (Jake Archibald)
Jake Archibald

開發人員可以做好準備,為網站開發史上的關鍵時刻做好準備。

[鼓聲響起]

Promise 已抵達 JavaScript!

[煙火爆炸、從空中飄揚的閃耀紙雨,人群變得狂野]

現階段,你屬於以下其中一個類別:

  • 大家都在熱烈鼓勵您,但不確定誰在乎什麼。也許您根本不確定「承諾」是什麼,所以您可能很聳動,但亮粉紙張的重量卻 在您肩膀上。如果是的話,不用擔心,我花了好多時間研究為何應該關注這類事情。建議您從「開頭」開始著手。
  • 你擊掌了!時間不對嗎?您之前曾使用過這些 Promise,但還是讓您感到所有實作的 API 略有不同。官方 JavaScript 版本的 API 是什麼?建議您從術語開始著手。
  • 您是知道這件事,也想在那些跳起和下手的民眾上競速,感覺就像他們聽的新聞一樣。請花點時間決定自身優勢,然後直接前往 API 參考資料頁面。

瀏覽器支援和 polyfill

瀏覽器支援

  • 32
  • 12
  • 29
  • 8

資料來源

如要使瀏覽器缺乏完整承諾實作,以符合規格要求,或在其他瀏覽器和 Node.js 中加入承諾,請參閱 polyfill (2 k gzip 壓縮)。

小事一團亂,

JavaScript 採用單一執行緒,表示兩組指令碼無法同時執行,必須依序執行。在瀏覽器中,JavaScript 共用一個執行緒,其中包含的載入項目 (視瀏覽器和瀏覽器而定)。不過,JavaScript 與繪製、更新樣式及處理使用者動作 (例如醒目顯示文字及與表單控制項互動) 位於同一個佇列中。其中一項活動會延誤其他活動。

身為人類的你,你需要採用多執行緒。您可以用多指輸入內容 可以同時行駛及按住對話我們要處理的唯一阻斷函式就是打噴嚏,此時所有目前活動都必須暫停執行。這很令人困擾 尤其在開車和嘗試保留對話時您不想再撰寫無害的程式碼。

您可能可能已經使用事件和回呼來解決這個問題。活動內容如下:

var img1 = document.querySelector('.img-1');

img1.addEventListener('load', function() {
  // woo yey image loaded
});

img1.addEventListener('error', function() {
  // argh everything's broken
});

這完全沒有打噴嚏。取得圖片後,加入幾個事件監聽器後,JavaScript 就會停止執行,直到呼叫其中一個事件監聽器為止。

遺憾的是,在上述範例中,事件可能會在我們監聽之前發生,因此我們必須使用圖片的「complete」屬性來解決這個問題:

var img1 = document.querySelector('.img-1');

function loaded() {
  // woo yey image loaded
}

if (img1.complete) {
  loaded();
}
else {
  img1.addEventListener('load', loaded);
}

img1.addEventListener('error', function() {
  // argh everything's broken
});

這個方法並無法擷取到收到監聽前就發生錯誤的圖片;遺憾的是,DOM 沒有提供上述方法。這麼做也會載入一個映像檔如果我們想知道何時載入一組圖片,情況會更複雜。

活動未必是最佳選擇

對於同一個物件 (keyuptouchstart 等) 中可能多次發生的事件,事件就非常適合。使用這類事件,您就不必在附加事件監聽器之前瞭解發生的情況。但是若是非同步的成功/失敗問題 最好會像這樣:

img1.callThisIfLoadedOrWhenLoaded(function() {
  // loaded
}).orIfFailedCallThis(function() {
  // failed
});

// and…
whenAllTheseHaveLoaded([img1, img2]).callThis(function() {
  // all loaded
}).orIfSomeFailedCallThis(function() {
  // one or more failed
});

這只是我們保證的成果,但命名方式更佳。如果 HTML 圖片元素具有傳回 Claim,我們就可以執行以下動作:

img1.ready()
.then(function() {
  // loaded
}, function() {
  // failed
});

// and…
Promise.all([img1.ready(), img2.ready()])
.then(function() {
  // all loaded
}, function() {
  // one or more failed
});

基本上,這個功能有點像事件監聽器,但下列情況除外:

  • 承諾只能成功或失敗一次。測試成功或失敗兩次,則不能從成功轉變到失敗,反之亦然。
  • 如果承諾成功或失敗,且您之後新增成功/失敗回呼,系統就會呼叫正確的回呼,即使事件較早發生也一樣。

這對非同步成功/失敗來說非常有用,因為您比較不太關注應用程式取得的確切時間,而且較有興趣回應結果。

Promise 術語

Domenic Denicola 證明會閱讀本文的第一份草稿,並將我評為「F」級別。他為了我心懷感想,強迫我複製美國國家命運儘管如此,我還是有很多術語 但重點是以下幾點:

承諾可以是:

  • fulfill:與承諾相關的動作相關動作
  • rejected:與承諾相關的動作失敗
  • 待處理 - 尚未完成或遭拒
  • 已設定 - 已完成或遭拒

規格也會使用「thenable」一詞來描述與承諾會類似的物件,因為其具有 then 方法。這個字詞提醒我前英格蘭足球經理 Terry Venables 的提醒,因此我會盡量少用它。

Promise 內建 JavaScript!

Promise 已使用資料庫一段時間,例如:

上述和 JavaScript 承諾共用一個稱為 Promises/A+ 的常見標準化行為。如果您是 jQuery 使用者,其具有類似的名稱,稱為延遲。不過,延遲功能並不符合 Promise/A+ 規範,因此功能稍有不同且較不實用,因此請謹慎使用。jQuery 也有「承諾類型」,但這只是「延遲」的子集,也會有相同的問題。

雖然承諾實作遵循標準化行為,但其整體 API 有所不同。JavaScript 承諾在 API 中與 RSVP.js 類似。如何建立承諾:

var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then…

  if (/* everything turned out fine */) {
    resolve("Stuff worked!");
  }
  else {
    reject(Error("It broke"));
  }
});

承諾建構函式使用一個引數 (具有兩個參數的回呼),解析和拒絕。在回呼中執行操作 (可能是非同步),然後在一切都正常運作時呼叫 解決,否則呼叫拒絕。

如同純舊版 JavaScript 中的 throw,此為自訂 (但並非強制) 使用 Error 物件拒絕。Error 物件的好處是擷取堆疊追蹤,讓偵錯工具更實用。

以下說明如何使用這項承諾:

promise.then(function(result) {
  console.log(result); // "Stuff worked!"
}, function(err) {
  console.log(err); // Error: "It broke"
});

then() 會使用兩個引數,一個用於成功案例的回呼,另一個則用於失敗案例。兩者都是選用項目,因此您只能新增成功或失敗情況的回呼。

JavaScript 承諾在 DOM 一開始時為「Futures」,重新命名為「Promises」,最後則移至 JavaScript。將這類檔案放在 JavaScript 而非 DOM 上是非常實用的做法,因為可以在 Node.js 等非瀏覽器的 JS 環境中使用 (無論是否在核心 API 中使用這類程式碼)。

雖然 DOM 是 JavaScript 功能,但我們不建議使用。事實上,所有採用非同步成功/失敗方法的新 DOM API 都會使用 Pro 承諾。配額管理字型載入事件ServiceWorkerWeb MIDI串流等都會發生這種情形。

與其他程式庫的相容性

JavaScript 保證 API 會將任何含有 then() 方法視為承諾型 (或承諾使用「thenable」中的「thenable」) 的情況,因此,如果您使用的程式庫會傳回 Q promise,就不必擔心,這和新的 JavaScript 承諾會很順手。

不過,如我所說,jQuery 的延遲比較有點...沒有幫助。幸好,您可以將這些程式碼轉換為標準承諾,以便盡快這麼做:

var jsPromise = Promise.resolve($.ajax('/whatever.json'))

這裡 jQuery 的 $.ajax 會傳回 Deferred。由於它採用 then() 方法,因此 Promise.resolve() 可將其轉換為 JavaScript 承諾。不過,有時延遲會將多個引數傳遞至回呼,例如:

var jqDeferred = $.ajax('/whatever.json');

jqDeferred.then(function(response, statusText, xhrObj) {
  // ...
}, function(xhrObj, textStatus, err) {
  // ...
})

儘管 JS 保證能忽略所有其他項目,但第一個例外:

jsPromise.then(function(response) {
  // ...
}, function(xhrObj) {
  // ...
})

幸好這通常是您想要的結果,或者至少可讓您存取自己想要的內容。另請注意,jQuery 不會遵循將 Error 物件傳遞至遭拒的慣例。

簡化複雜的非同步程式碼

好,我們來編寫程式碼假設我們想要:

  1. 啟動旋轉圖示以表示正在載入
  2. 擷取故事的 JSON 檔案,提供每個章節的標題與網址
  3. 新增頁面標題
  4. 擷取每個章節
  5. 將精選故事新增至頁面
  6. 停止旋轉圖示

...同時告知使用者過程中是否有錯誤。我們希望在該時候停止旋轉圖示,否則會保持旋轉、暈眩,並當機到其他 UI。

當然,請勿使用 JavaScript 來傳遞故事,以 HTML 的速度提供更快速,但這種模式在處理 API 時很常見:多次擷取資料,然後在作業完成後進行某些操作。

首先,讓我們從網路擷取資料:

證明 XMLHttpRequest

如果能以回溯相容的方式,更新舊 API 以使用 Proto。XMLHttpRequest 是最佳選項,但目前,我們來編寫一個簡單的函式來發出 GET 要求:

function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.response);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}

現在就來使用這個方法:

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
})

現在,我們不必手動輸入 XMLHttpRequest 即可發出 HTTP 要求,太棒了,因為無須再手動輸入 XMLHttpRequest 的駝峰式大小寫,對我來說會很開心。

鏈結

then() 不是故事的結尾,您可以將 then 鏈結在一起以轉換資料值,或是依序執行其他非同步動作。

轉換價值

您只要傳回新值即可轉換值:

var promise = new Promise(function(resolve, reject) {
  resolve(1);
});

promise.then(function(val) {
  console.log(val); // 1
  return val + 2;
}).then(function(val) {
  console.log(val); // 3
})

以下提供實際範例:

get('story.json').then(function(response) {
  console.log("Success!", response);
})

回應為 JSON,但目前我們能夠以純文字格式接收回應。我們可以修改 get 函式來使用 JSON responseType,但也可以在承諾環境中解決這個問題:

get('story.json').then(function(response) {
  return JSON.parse(response);
}).then(function(response) {
  console.log("Yey JSON!", response);
})

由於 JSON.parse() 使用單一引數並傳回轉換後的值,因此我們可以建立捷徑:

get('story.json').then(JSON.parse).then(function(response) {
  console.log("Yey JSON!", response);
})

事實上,我們可以輕鬆讓 getJSON() 函式執行以下操作:

function getJSON(url) {
  return get(url).then(JSON.parse);
}

getJSON() 仍會傳回承諾,即擷取網址並將回應剖析為 JSON。

將非同步動作排入佇列

您也可以鏈結 then,依序執行非同步動作。

當您透過 then() 回呼傳回內容時,可能會產生一些神奇的神奇效果。如果傳回值,系統會使用該值呼叫下一個 then()。不過,如果您傳回類似 promise 的內容,下一個 then() 會等待,並只在該承諾失敗 (成功/失敗) 時才呼叫。例如:

getJSON('story.json').then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  console.log("Got chapter 1!", chapter1);
})

我們會向 story.json 發出非同步要求,其中提供一組要要求的網址,然後再要求第一組網址。這時一定會有別於簡單的回呼模式。

你甚至可以建立快速取得章節的捷徑:

var storyPromise;

function getChapter(i) {
  storyPromise = storyPromise || getJSON('story.json');

  return storyPromise.then(function(story) {
    return getJSON(story.chapterUrls[i]);
  })
}

// and using it is simple:
getChapter(0).then(function(chapter) {
  console.log(chapter);
  return getChapter(1);
}).then(function(chapter) {
  console.log(chapter);
})

除非呼叫 getChapter,否則我們不會下載 story.json,但下次呼叫 getChapter 時,我們會重複使用故事承諾,因此 story.json 只會擷取一次。耶 Promise!

處理錯誤

如先前所述,then() 使用兩個引數,一個為成功,一個為失敗 (或在 promises-peak 中執行並拒絕):

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.log("Failed!", error);
})

您也可以使用 catch()

get('story.json').then(function(response) {
  console.log("Success!", response);
}).catch(function(error) {
  console.log("Failed!", error);
})

catch() 沒有特殊的理由,它只是 then(undefined, func) 的糖,但更易讀。請注意,上述兩個程式碼範例的行為不相同,後者等同於:

get('story.json').then(function(response) {
  console.log("Success!", response);
}).then(undefined, function(error) {
  console.log("Failed!", error);
})

兩者的差異不小,卻非常實用。Promise 遭拒會透過拒絕回呼 (或 catch(),因為對等回呼) 跳到下一個 then()。如果使用 then(func1, func2)func1func2 就不會同時呼叫兩者,但如果使用 then(func1).catch(func2),當 func1 拒絕時,系統都會呼叫兩者,因為它們是鏈結中的獨立步驟。請按照下列步驟操作:

asyncThing1().then(function() {
  return asyncThing2();
}).then(function() {
  return asyncThing3();
}).catch(function(err) {
  return asyncRecovery1();
}).then(function() {
  return asyncThing4();
}, function(err) {
  return asyncRecovery2();
}).catch(function(err) {
  console.log("Don't worry about it");
}).then(function() {
  console.log("All done!");
})

上述流程與一般 JavaScript try/catch 非常類似,在「嘗試」中發生的錯誤會立即移至 catch() 區塊。以下為流程圖 (因為我熱愛流程圖):

遵循藍線代表可完成的承諾,如為拒絕的承諾,則遵循紅色線條。

JavaScript 例外狀況和承諾

系統會在明確拒絕承諾時發出拒絕,但也會隱含在建構函式回呼中擲回錯誤的情況:

var jsonPromise = new Promise(function(resolve, reject) {
  // JSON.parse throws an error if you feed it some
  // invalid JSON, so this implicitly rejects:
  resolve(JSON.parse("This ain't JSON"));
});

jsonPromise.then(function(data) {
  // This never happens:
  console.log("It worked!", data);
}).catch(function(err) {
  // Instead, this happens:
  console.log("It failed!", err);
})

這表示在承諾建構函式回呼中執行所有與承諾相關的工作會很有用,因此系統會自動偵測錯誤並遭到拒絕。

then() 回呼中擲回的錯誤亦適用上述做法。

get('/').then(JSON.parse).then(function() {
  // This never happens, '/' is an HTML page, not JSON
  // so JSON.parse throws
  console.log("It worked!", data);
}).catch(function(err) {
  // Instead, this happens:
  console.log("It failed!", err);
})

實際處理錯誤

透過我們的故事和章節,我們可以使用「追捕」來向使用者顯示錯誤:

getJSON('story.json').then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  addHtmlToPage(chapter1.html);
}).catch(function() {
  addTextToPage("Failed to show chapter");
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

如果擷取 story.chapterUrls[0] 失敗 (例如 http 500 或使用者處於離線狀態),系統會略過後續的所有回呼,包括嘗試將回應剖析為 JSON 的 getJSON() 中的回呼,同時略過會在網頁中加入 Cha1.html 的回呼。而是會移到擷取回呼中。因此,如果先前的任何操作失敗,頁面就會新增「無法顯示章節」。

如同 JavaScript 的 try/catch,系統會擷取錯誤並繼續處理後續的程式碼,因此旋轉圖示會一律隱藏,這也是我們想要的做法。以上內容成為以下項目的非封鎖非同步版本:

try {
  var story = getJSONSync('story.json');
  var chapter1 = getJSONSync(story.chapterUrls[0]);
  addHtmlToPage(chapter1.html);
}
catch (e) {
  addTextToPage("Failed to show chapter");
}
document.querySelector('.spinner').style.display = 'none'

您可能只想使用 catch() 進行記錄,而不從錯誤復原。方法很簡單,只要再次擲回錯誤即可。我們可在 getJSON() 方法中執行此操作:

function getJSON(url) {
  return get(url).then(JSON.parse).catch(function(err) {
    console.log("getJSON failed for", url, err);
    throw err;
  });
}

我們已成功擷取一個章節,但我們希望所有章節都完成。我們來實現目標吧!

平行處理和序列:

進行非同步活動並不容易。如果您難以取捨,請嘗試以同步方式編寫程式碼。在這種情況下:

try {
  var story = getJSONSync('story.json');
  addHtmlToPage(story.heading);

  story.chapterUrls.forEach(function(chapterUrl) {
    var chapter = getJSONSync(chapterUrl);
    addHtmlToPage(chapter.html);
  });

  addTextToPage("All done");
}
catch (err) {
  addTextToPage("Argh, broken: " + err.message);
}

document.querySelector('.spinner').style.display = 'none'

這樣可以!但系統在下載內容時會同步處理並鎖定瀏覽器。若要讓這項工作能非同步執行,我們使用 then() 來逐一完成。

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  // TODO: for each url in story.chapterUrls, fetch & display
}).then(function() {
  // And we're all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector('.spinner').style.display = 'none';
})

但是,我們該如何循環播放章節網址,並依序擷取?這樣無效

story.chapterUrls.forEach(function(chapterUrl) {
  // Fetch chapter
  getJSON(chapterUrl).then(function(chapter) {
    // and add it to the page
    addHtmlToPage(chapter.html);
  });
})

forEach 無法採用非同步感知特性,因此我們的分支會依照下載的順序顯示,基本上就是 PulpTD 的書寫順序。這並非虛構,所以讓我們一起修正。

建立序列

我們希望將 chapterUrls 陣列轉換為承諾序列。我們可使用 then() 執行這項操作:

// Start off with a promise that always resolves
var sequence = Promise.resolve();

// Loop through our chapter urls
story.chapterUrls.forEach(function(chapterUrl) {
  // Add these actions to the end of the sequence
  sequence = sequence.then(function() {
    return getJSON(chapterUrl);
  }).then(function(chapter) {
    addHtmlToPage(chapter.html);
  });
})

這是我們第一次偵測到 Promise.resolve(),這會建立承諾,可解析提供的值。如果您將其傳遞給 Promise 的執行個體,系統只會傳回該例項 (注意:這是部分實作尚未遵循的規格變更)。如果您傳遞了像承諾之類的內容 (使用 then() 方法),則系統會建立實際的 Promise,並以相同的方式執行/拒絕。如果您傳入任何其他值 (例如Promise.resolve('Hello'),就會建立具有該值的承諾。如果呼叫函式沒有值 (如上所述),就會執行「未定義」。

也有 Promise.reject(val),其會建立承諾會拒絕並顯示您提供的值 (或未定義)。

我們可以使用 array.reduce 來整理上述程式碼:

// Loop through our chapter urls
story.chapterUrls.reduce(function(sequence, chapterUrl) {
  // Add these actions to the end of the sequence
  return sequence.then(function() {
    return getJSON(chapterUrl);
  }).then(function(chapter) {
    addHtmlToPage(chapter.html);
  });
}, Promise.resolve())

這項操作與上一個範例相同,但不需要獨立的「sequence」變數。系統會針對陣列中的每個項目呼叫縮減回呼方法。「Serial」是第一次呼叫 Promise.resolve(),但對於「sequence」指令的其餘呼叫,則是指我們先前呼叫傳回的內容。array.reduce 對於將陣列向下繫結為單一值非常有用,在本例中為承諾。

讓我們總結:

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  return story.chapterUrls.reduce(function(sequence, chapterUrl) {
    // Once the last chapter's promise is done…
    return sequence.then(function() {
      // …fetch the next chapter
      return getJSON(chapterUrl);
    }).then(function(chapter) {
      // and add it to the page
      addHtmlToPage(chapter.html);
    });
  }, Promise.resolve());
}).then(function() {
  // And we're all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector('.spinner').style.display = 'none';
})

這就是同步版本的完整同步版本。但我們可以做得更好目前,我們的網頁下載如下:

瀏覽器很擅長下載多種內容,因此我們會逐一下載章節,因此失去效能。我們想要一次下載所有這些檔案,然後在所有版本送達時進行處理。幸好有 API 可以達成下列目的:

Promise.all(arrayOfPromises).then(function(arrayOfResults) {
  //...
})

Promise.all 會接受一系列承諾,並在所有承諾皆成功完成時建立承諾。您會取得一系列結果 (不論所做承諾為何),這些結果的順序與您傳入的承諾順序相同。

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  // Take an array of promises and wait on them all
  return Promise.all(
    // Map our array of chapter urls to
    // an array of chapter json promises
    story.chapterUrls.map(getJSON)
  );
}).then(function(chapters) {
  // Now we have the chapters jsons in order! Loop through…
  chapters.forEach(function(chapter) {
    // …and add to the page
    addHtmlToPage(chapter.html);
  });
  addTextToPage("All done");
}).catch(function(err) {
  // catch any error that happened so far
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

視連線狀況而定,這比逐一載入快的速度快上 秒,且比第一次嘗試載入的程式碼少很多。章節可以按任何順序下載,但這些章節會以正確的順序顯示。

不過,我們仍然可以提升感知的效能。當第一章抵達時 我們應該在頁面中加入這段如此一來,使用者就能在剩餘的章節結束前開始閱讀。當第 3 章抵達時,我們不會將其加入頁面,因為使用者可能不知道第 2 章。第 2 章之後,即可新增第二章、第三章,以此類推。

為此,我們會同時擷取所有章節的 JSON 檔案,然後建立序列,將這些章節加入文件中:

getJSON('story.json')
.then(function(story) {
  addHtmlToPage(story.heading);

  // Map our array of chapter urls to
  // an array of chapter json promises.
  // This makes sure they all download in parallel.
  return story.chapterUrls.map(getJSON)
    .reduce(function(sequence, chapterPromise) {
      // Use reduce to chain the promises together,
      // adding content to the page for each chapter
      return sequence
      .then(function() {
        // Wait for everything in the sequence so far,
        // then wait for this chapter to arrive.
        return chapterPromise;
      }).then(function(chapter) {
        addHtmlToPage(chapter.html);
      });
    }, Promise.resolve());
}).then(function() {
  addTextToPage("All done");
}).catch(function(err) {
  // catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

馬上開始,最棒的是!交付所有內容所需的時間相同,但使用者更快收到內容。

在這個小型範例中,所有章節大約會在同一時間到達,但一次顯示一個章節的好處會更加明顯。

使用 Node.js 樣式回呼或事件執行上述操作會大約兩倍程式碼,但更重要的是無法輕鬆跟上。但可說並不是故事的尾聲,只要搭配其他 ES6 功能,就能更輕鬆。

額外機會:更強大的功能

自我最初撰寫這篇文章後,使用 Promise 的功能已大幅擴張。自 Chrome 55 版起,非同步函式允許將承諾型程式碼編寫為同步程式碼,但不會封鎖主執行緒。詳情請參閱my async functions article。Promise 和非同步函式目前已廣泛支援主要瀏覽器。您可以在 MDN 的 Promise非同步函式參考資料中找到詳細資料。

感謝 Anne van Kesteren、Domenic Denicola、Tom Ashworth、Remy Sharp、Addy Osmani、Arthur Evans 和 Yutaka Hirano 的成就,他們會校實並做出修正/建議。

此外,還要感謝 Mathias Bynens 更新文章的各個部分