Skip to content
学习 衡量 博客 Case studies 关于
本页内容
  • 有什么好大惊小怪的?
  • Promise 术语
  • Promise 来到了 JavaScript!
  • 浏览器支持和 polyfill
  • 与其他库的兼容性
  • 将复杂的异步代码简单化
  • 将 XMLHttpRequest Promise 化
  • 链接
    • 转换值
    • 队列异步操作
  • 错误处理
    • JavaScript 异常和 promise
    • 实践中的错误处理
  • 并行和排序:两者兼得
    • 创建序列
  • 奖励回合:扩展能力

JavaScript Promises:简介

Promise 简化了延迟和异步计算。Promise 代表一个尚未完成的操作。

Dec 16, 2013 — 更新日期 Jan 18, 2021
Available in: Español, 日本語, 한국어, Português, Русский, English
Jake Archibald
Jake Archibald
TwitterHomepage
本页内容
  • 有什么好大惊小怪的?
  • Promise 术语
  • Promise 来到了 JavaScript!
  • 浏览器支持和 polyfill
  • 与其他库的兼容性
  • 将复杂的异步代码简单化
  • 将 XMLHttpRequest Promise 化
  • 链接
    • 转换值
    • 队列异步操作
  • 错误处理
    • JavaScript 异常和 promise
    • 实践中的错误处理
  • 并行和排序:两者兼得
    • 创建序列
  • 奖励回合:扩展能力

诸位开发同仁,请准备迎接 Web 开发史上的重大时刻。

[鼓声响起来]

Promise 已经来到了 JavaScript!

[璀璨的烟花在天上亮起,五彩纸屑纷纷落下,人们为之疯狂]

此时此刻,您会是下列几种人之一:

  • 大家在周围欢呼,但您根本不知道这是为了什么。也许您甚至不知道这个 promise 到底是什么东西。您耸了耸肩,但肩上却感到了五彩纸屑的重量。假如是这样,请别担心,我花了很长时间才弄清楚它的重要性。您可能想从头开始了解。
  • 您兴奋地挥出一拳!可算等到了!您之前已经用过这些 Promise,但让人苦恼的是,所有的实现都采用略有不同的 API。官方 JavaScript 版本的 API 是什么?您可能想从术语开始。
  • 您对它已经有所了解了,对那些上蹿下跳的新人嗤之以鼻。花点时间享受下您的优越感,然后直接前往 API 参考吧。

有什么好大惊小怪的? #

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 并没有给我们提供解决的办法。此外,这还只是加载一张图片。如果我们想知道一组图像在何时加载,情况就会变得更加复杂。

事件并非万能 #

事件对可以在同一个对象上发生多次的活动是个好东西,比如 keyup , touchstart 等。有了那些事件,就无需在添加侦听器前了解到底发生了什么事。但在涉及到异步成功/失败时,理想情况下,您需要用到下列代码:

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

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

这就是 Promise 的作用,但命名更好了。假如 HTML 图像元素有一个返回 promise 的 ready 方法,我们可以这样做:

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

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

从本质看,promise 有点像事件监听器,只不过:

  • Promise 只能成功或失败一次。它不能成功或失败两次,也不能从成功切换到失败,反之亦然。
  • 如果 Promise 成功或失败,并且您稍后添加成功/失败回调,则将调用正确的回调,即使该事件发生的时间更早。

这对于异步成功/失败非常有用,因为您对某些操作的确切时间没兴趣,反而对结果的反应更感兴趣。

Promise 术语 #

Domenic Denicola 校对阅读了本文的初稿,并评价我的术语根本不及格。他把我关了起来,罚我抄了 100 遍的《States and Fates》,还给我的父母写了一封信表示担忧。尽管如此,我仍然弄混了很多术语,但下面列出了基本知识:

Promise 可以是:

  • 已完成- 与 promise 相关的操作成功
  • 已拒绝- 与 promise 相关的操作失败
  • 待定- 尚未完成或已拒绝
  • 已解决- 已完成或已拒绝

本规范还使用术语 thenable 来描述一个拥有 then 方法的类 promise 对象。这个术语总是让我想起前英格兰足球经理 Terry Venables,所以我会尽量少用它。

Promise 来到了 JavaScript! #

Promise 已经以库的形式出现了一段时间,例如:

  • Q
  • when
  • WinJS
  • RSVP.js

以上列出的库和 JavaScript Promise 共享一个通用的标准化行为,称之为 Promises/A+ 。如果您是 jQuery 用户,它们也有一个叫做 Deferreds 的类似行为。然而,Deferreds 与 Promise/A+ 并不兼容,这使得它们略有不同,而且用途更少,所以要小心。 jQuery 也有一个 Promise 类型,但它只是 Deferred 的一个子集,并且存在同样的问题。

尽管 promise 实现遵循标准化行为,但它们的整体 API 各不相同。 JavaScript promise 在 API 方面与 RSVP.js 类似。下面列出了创建 promise 的方法:

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"));
}
});

promise 构造函数使用了一个参数,一个带有两个参数 resolve 和 reject 的回调。在回调中执行一些操作,比如异步;然后如果一切顺利,就调用 resolve,否则会调用 reject。

与 JavaScript 中的 throw 类似,使用 Error 对象拒绝是一种常见而非必须的做法。使用 Error 对象的好处在于它们可以捕获堆栈跟踪,从而更好地发挥调试工具的作用。

下面列出了使用 promise 的方法:

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

then()有两个参数,一个成功案例的回调,另一个是失败案例的回调。这两者都是可选项,因此您只能为成功案例或失败案例添加回调。

JavaScript promise 在 DOM 中最初叫做 Futures,后来改名为 Promises,并最终进入了 JavaScript。在 JavaScript 中使用它们比在 DOM 中使用更好,因为它们可用于非浏览器 JS 上下文中,例如 Node.js(至于是否在其核心 API 中使用,则是另一个问题了)。

尽管它们是 JavaScript 的功能,但 DOM 也可以使用。事实上,所有具有异步成功/失败方法的新 DOM API 都将使用 Promise。这在 Quota Management 、Font Load Events 、ServiceWorker 、Web MIDI 和 Streams 等中已经很常见了。

浏览器支持和 polyfill #

现在,浏览器已经中实现了 promise。

从 Chrome 32、Opera 19、Firefox 29、Safari 8 和 Microsoft Edge 开始,promises 是默认开启的。

若要使缺乏完整 promise 实现的浏览器符合规范,或将 promise 添加到其他浏览器和 Node.js 中,请查看polyfill (2k gzipped)。

与其他库的兼容性 #

JavaScript promise API 会将任何使用 then() 方法的对象当做类 promise(或者在 promise 中叫 thenable,唉)来处理,所以如果您使用返回 Q promise 的库,也没关系,它将与新的 JavaScript promise 良好兼容。

不过就像我之前说得那样,jQuery 的 Deferreds 的作用不大。但幸好您可以将它们转换为标准 promise,而这值得尽快完成:

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

在这里,jQuery 的$.ajax返回一个 Deferred。因为它使用了一个 then() 方法, Promise.resolve() 可以把它变成 JavaScript promise。但是,有时 deferred 会向其回调传递多个参数,例如:

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

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

而 JS promise 会忽略除第一个之外的所有参数:

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

值得庆幸的是,这通常是您想要的结果,或者至少可以让您访问想要的内容。另外请注意,jQuery 不遵循将 Error 对象传递给拒绝的约定。

将复杂的异步代码简单化 #

好吧,我们来写些代码。比方说,我们想要:

  1. 启动一个旋转器,表明正在加载的过程
  2. 为一个故事获取几个 JSON,为我们提供标题以及每章的 url
  3. 给页面添加标题
  4. 获取每一章
  5. 将故事添加到页面
  6. 停止旋转器

……但也要告诉用户中途是否出现问题。假如出现问题,我们也需要在那时停止旋转器,否则它会继续旋转,并与其他用户界面发生冲突。

当然,您不会使用 JavaScript 来传递故事,因为使用 HTML 会更快,但这种模式在处理 API 时很常见:多次获取数据,然后在所有数据获取完毕后执行操作。

首先,让我们处理从网络获取数据:

将 XMLHttpRequest Promise 化 #

如果可能采取向后兼容的方式,那么可以更新旧的 API 来使用 Promise。 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 ,但同时也可以在 promise 领域解决它:

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() 依然会返回一个 promise,它获取一个 url,然后将响应解析为 JSON。

队列异步操作 #

您还可以链接then以按顺序运行异步操作。

当 then() 回调返回结果时,这有些神奇。如果返回的是一个值,那么将使用该值调用下一个 then()。但如果返回的是类 promise 的内容,那么下一个 then() 会等待它,并且仅在该 promise 解决(成功/失败)时调用。例如:

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

在这里,我们向 story.json 发出一个异步请求,它为我们提供了一组要请求的 URL,然后我们请求其中第一个 URL。就是在这时候,promise 开始从简单的回调模式中脱颖而出。

您甚至可以生成一个获取章节的快捷方法:

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 的时候,我们可以重复使用故事的 promise,所以只用获取一次 story.json。Promise,真棒!

错误处理 #

正如之前看到的, then() 有两个参数,分别表示成功和失败(promises 的术语叫做完成和拒绝):

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);
})

这种差异很微妙,但非常有用。 当出现拒绝回调(或 catch() ,因为它是等效的)时,promise 拒绝会跳到下一个 then()。这时会调用 then(func1, func2) 、func1 或 func2,但不会同时调用两个。但是对于 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 流程非常相似,在 try 中发生的错误会立即进入 catch() 块。这是上面的流程图(因为我喜欢流程图):

蓝线通往完成的 promise,红线通往拒绝的 promise。

JavaScript 异常和 promise #

当明确拒绝 promise 时就会出现拒绝,此外,当在构造函数回调中引发错误时,也会暗示出现拒绝:

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);
})

这意味着在 Promise 构造函数回调中完成所有与 Promise 相关的工作非常有用,这样一来,错误会被自动捕获并变成拒绝。

在 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);
})

实践中的错误处理 #

对于故事和章节,可以使用 catch 向用户显示错误:

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(),同时还会跳过将 chapter1.html 添加到页面的回调。相反,它移动到 catch 回调。因此,如果之前的任何操作均失败,那么”无法显示章节“将添加到页面中。

和 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';
})

但如何遍历章节 url 并按顺序获取它们呢?这样行不通:

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

forEach 不是异步感知的,所以章节会按照它们的下载的顺序出现,这基本上就是电影《低俗小说》的写作方式。可我们不是在写剧本,所以得解决这个问题。

创建序列 #

我们想把 chapterUrls 数组变成一个 promise 序列。可以使用 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。如果您传递给它一个 Promise 实例,它会返回该实例(**注意:**这是对规范的一个更改,某些实现尚未遵循)。如果您向它传递一些类 Promise(拥有 then() 方法),它会创建一个真正的 Promise,以相同的方式完成/拒绝。如果您传入任何其他值,例如 Promise.resolve('Hello') ,它会创建一个满足该值的 promise。如果您不用任何值调用它,如上所述,它将以“未定义”来完成。

另外还有 Promise.reject(val) ,它创建了一个拒绝您赋予值(或未定义)的 promise。

我们可以使用 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 变量。为数组中的每个项目调用我们的 reduce 回调。第一次调用时,sequence 是 Promise.resolve() ,但对于其余的调用,sequence 是我们从上一次调用返回的任何内容。array.reduce 对于将数组简化为单个值非常有用,在这种情况下这个值就是 promise。

现在把它放在一起:

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 使用一个数组的 promise,并所有 promise 成功完成后创建一个已完成的 promise。您会以传入的 promise 相同的顺序获得一系列结果(无论 promise 完成到什么程度)。

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';
})

根据连接情况,这可能比逐个加载快几秒钟,并且比第一次尝试使用的代码更少。章节可以按任何顺序下载,但它们会以正确的顺序出现在屏幕上。

但是,我们仍然可以提高感知性能。当第一章下载完毕时,我们应该将其添加到页面中。这让用户可以在其余章节下载完毕之前开始阅读。当第三章到达时,我们不会将其添加到页面中,因为用户可能没有意识到第二章还未准备好。当第二章到来时,我们就可以添加第二章和第三章了,以此类推。

为此,我们同时为所有章节获取 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 样式的回调或事件执行上述操作大约会有两倍的代码量,但更重要的是,它的遵循难度更高。然而,Promise 的故事还没有结束,当与其他 ES6 特性结合时,它们会变得更加容易。

奖励回合:扩展能力 #

自从我最初写这篇文章以来,使用 Promise 的能力已经得到了极大的扩展。从 Chrome 55 开始,异步函数允许以同步方式编写基于 Promise 的代码,但不会阻塞主线程。您可以在我的异步函数一文中查看更多相关信息。主流浏览器都广泛支持 Promise 和 async 函数。您可以在 MDN 的 Promise 和 异步函数参考中找到详细信息。

非常感谢 Anne van Kesteren、Domenic Denicola、Tom Ashworth、Remy Sharp、Addy Osmani、Arthur Evans 和 Yutaka Hirano,他们校对了本文并提出了更正/建议。

另外,感谢 Mathias Bynens 对本文多个部分的更新工作。

JavaScript
Last updated: Jan 18, 2021 — Improve article
分享
订阅

Contribute

  • 提交错误
  • 查看源代码

相关内容

  • developer.chrome.com
  • Chrome 动态
  • 网站开发基础
  • 案例研究
  • 播客
  • 节目

连接

  • Twitter
  • YouTube
  • Google Developers
  • Chrome
  • Firebase
  • Google Cloud Platform
  • 所有产品
  • 条款和隐私权
  • 社区准则

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies.