应许之地 ( The Promised Land )
Native Promises are amongst the biggest changes ES2015 make to the JavaScript landscape. They eliminate some of the more substantial problems with callbacks, and allow us to write asynchronous code that more nearly abides by synchronous logic.
原生承诺是ES2015对JavaScript领域做出的最大改变之一。 它们消除了回调中的一些更实质性的问题,并允许我们编写更接近同步逻辑的异步代码。
It's probably safe to say that promises, together with generators, represent the New Normal™ of asyc. Whether you use them or not, you've got to understand them.
可以肯定地说,promise与生成器一起代表了asyc的New Normal™。 不管您是否使用它们,都必须了解它们。
Promises feature a fairly simple API, but come with a bit of a learning curve. They can be conceptually exotic if you've never seen them before, but all it takes to wrap your head around them is a gentle introduction and ample practice.
Promises具有相当简单的API,但有一些学习曲线。 如果您以前从未看过它们,那么它们在概念上可能是异国情调的,但是将头缠绕在它们周围所要做的只是轻轻的介绍和充分的练习。
By the end of this article, you'll be able to:
到本文结尾,您将能够:
- Articulate why we have promises, and what problems they solve; 阐明我们为什么有承诺,以及他们解决了什么问题;
- Explain what promises are, from the perspective both of their implementation and their usage; and 解释什么承诺是,无论从它们的实现及其用法的观点; 和
- Reimplement common callback patterns using promises. 使用promise重新实现常见的回调模式。
Oh, one note. The examples assume you're running Node. You can copy/paste the scripts manually, or clone my repo to save the trouble.
哦,一个音符。 这些示例假定您正在运行Node。 您可以手动复制/粘贴脚本,也可以克隆我的存储库以免麻烦。
Just clone it down and checkout the Part_1
branch:
只需将其克隆下来并检出Part_1
分支即可:
git clone https://github.com/Peleke/promises/
git checkout Part_1-Basics
. . . And you're good to go. The following is our outline for this path of promises:
。 。 。 而且你很好。 以下是我们对这一承诺之路的概述:
- The Problem with Callbacks 回调问题
- Promises: Definitions w/ Notes from the A+ Spec 承诺:带有A +规范注释的定义
- Promises & Un-inversion of Control 承诺与控制不可逆转
- Control Flow with Promises 用承诺控制流程
- Grokking
then
,reject
, &resolve
then
吟,reject
和resolve
异步性 ( Asynchronicity )
If you've spent any time at all with JavaScript, you've probably heard that it's fundamentally non-blocking, or asynchronous. But what doe that mean, exactly?
如果您花了所有时间在JavaScript上,那么您可能已经听说过它从根本上说是非阻塞的或异步的 。 但是,那到底是什么意思呢?
同步与异步 (Sync & Async)
Synchronous code runs before any code that follows it. You'll also see the term blocking as a synonym for synchronous, since it block the rest of the program from running until it finishes.
同步代码 先于其后的任何代码运行。 您还将看到“ 阻塞 ”一词作为“同步”的同义词,因为它会阻止程序的其余部分运行直到完成。
// readfile_sync.js
"use strict";
// This example uses Node, and so won't run in the browser.
const filename = 'text.txt',
fs = require('fs');
console.log('Reading file . . . ');
// readFileSync BLOCKS execution until it returns.
// The program will wait to execute anything else until this operation finishes.
const file = fs.readFileSync(`${__dirname}/${filename}`);
// This will ALWAYS print after readFileSync returns. . .
console.log('Done reading file.');
// . . . And this will ALWAYS print the contents of 'file'.
console.log(`Contents: ${file.toString()}`);
![Predictable results from readFileSync.](https://i-blog.csdnimg.cn/blog_migrate/89ea2c073c2002d70e5149c7f54d21b0.png)
Asynchronous code is just the opposite: It allows the rest of the program to execute while it handles long-running operations, such as I/O or network operations. This is also called non-blocking code. Here's the asynchronous analogue of the above snippet:
异步代码正好相反:它允许程序的其余部分在处理长时间运行的操作(例如I / O或网络操作)时执行。 这也称为非阻塞代码 。 这是上述代码段的异步模拟:
// readfile_async.js
"use strict";
// This example uses Node, so it won't run in the browser.
const filename = 'text.txt',
fs = require('fs'),
getContents = function printContent (file) {
try {
return file.toString();
} catch (TypeError) {
return file;
}
}
console.log('Reading file . . . ');
console.log("=".repeat(76));
// readFile executes ASYNCHRONOUSLY.
// The program will continue to execute past LINE A while
// readFile does its business. We'll talk about callbacks in detail
// soon -- for now, just pay mind to the the order of the log
// statements.
let file;
fs.readFile(`${__dirname}/${filename}`, function (err, contents) {
file = contents;
console.log( `Uh, actually, now I'm done. Contents are: ${ getContents(file) }`);
}); // LINE A
// These will ALWAYS print BEFORE the file read is complete.
// Well, that's both misleading and useless.
console.log(`Done reading file. Contents are: ${getContents(file)}`);
console.log("=".repeat(76));
![Async I/O can make for confusing results.](https://i-blog.csdnimg.cn/blog_migrate/85f446652a1e716337063ee6f5dfc6a9.png)
The major advantage to synchronous code is that it's easy to read and reason about: Synchronous programs execute from top to bottom, and line n finishes before line n + 1. Period.
同步代码的主要优点是易于阅读和推理:同步程序从上到下执行,第n行在第n + 1行之前结束。 期。
The major disadvantage is that synchronous code is slow—often debilitatingly so. Freezing the browser for two seconds every time your user needs to hit the server makes for a lousy user experience.
主要的缺点是同步代码很慢-常常使人感到衰弱。 每次您的用户需要访问服务器时,将浏览器冻结两秒钟会带来糟糕的用户体验。
And this, mes amis, is why JavaScript is non-blocking at the core.
这就是mes amis的原因,这就是JavaScript处于非阻塞状态的原因。
异步的挑战 (The Challenge of Asynchronicity)
Going async buys us speed, but costs us linearity. Even the trivial script above demonstrates this. Note that:
进行异步可以提高速度,但会降低线性度。 甚至上面的琐碎脚本也证明了这一点。 注意:
- There's no way to know when
file
will be available, other than handing control toreadFile
and letting it notify us when it's ready; and 有没有办法知道什么时候file
将可用,除了移交控制readFile
, 让它通知我们当它准备; 和 - Our program no longer executes the way it reads, which makes it harder to reason about. 我们的程序不再执行其读取方式,这使推理变得更加困难。
These problems alone are enough to occupy us for the rest of this article.
仅这些问题就足以占据本文其余部分的位置。
回调和后备 ( Callbacks & Fallbacks )
Let's strip our async readFile
example down a bit.
让我们简化一下异步readFile
示例。
"use strict";
const filename = 'throwaway.txt',
fs = require('fs');
let file, useless;
useless = fs.readFile(`${__dirname}/${filename}`, function callback (error, contents) {
file = contents;
console.log( `Got it. Contents are: ${contents}`);
console.log( `. . . But useless is still ${useless}.` );
});
// Thanks to Rava for catching an error in this line.
console.log(`File is ${useless}, but that'll change soon.`);
Since readFile
is non-blocking, it must return immediately for the program to continue to execute. Since Immediately isn't enough time to perform I/O, it returns undefined
, and we execute as much as we can until readFile
finishes . . . Well, reading the file.
由于readFile
是非阻塞的,因此它必须立即返回以使程序继续执行。 由于Instantly没有足够的时间执行I / O,因此它返回undefined
,因此我们将尽可能多地执行直到readFile
完成。 。 。 好了,读取文件。
The question is, how do we know when the read is complete?
问题是, 我们如何知道读取完成的时间 ?
Unfortunately, we can't. But readFile
can. In the snippet above, we've passed readFile
two arguments: A filename, and a function, called a callback, which we want to execute as soon as the read is finished.
不幸的是, 我们不能。 但是readFile
可以。 在上面的代码段中,我们为readFile
传递了两个参数:文件名和一个名为callback的函数,我们希望在读取完成后立即执行它们。
In English, this reads something like: "readFile
; see what's inside of ${__dirname}/${filename}
, and take your time. Once you know, run this callback
with the contents
, and let me know if there was an error
."
用英语,它的内容类似于:“ readFile
;查看${__dirname}/${filename}
,然后花点时间。一旦知道,请使用contents
运行此callback
,并让我知道是否有error
”。
The important thing to take away is that we can't know when the file contents are ready: Only readFile
can. That's why we hand it our callback, and trust it to do the right thing with it.
要带走的重要一点是, 我们不知道文件内容何时准备就绪:只有readFile
可以。 这就是为什么我们将其传递给回调函数,并信任它可以对其执行正确的操作。
This is the pattern for dealing with asynchronous functions in general: Call it with parameters, and pass it a callback to run with the result.
通常,这是处理异步函数的模式:使用参数调用它,然后将其传递给回调以运行结果。
Callbacks are a solution, but they're not perfect. Two bigger problems are:
回调是一种解决方案,但并不完美。 两个更大的问题是:
- Inversion of control; and 控制反转; 和
- Complicated error handling. 复杂的错误处理。
控制反转 (Inversion of Control)
The first problem is one of trust.
第一个问题是信任。
When we pass readFile
our callback, we trust it will call it. There is absolutely no guarantee it actually will. Nor is there any guarantee that, if it does call, that it will be with the right parameters, in the right order, the right number of times.
当我们将readFile
传递给回调readFile
,我们相信它将调用它。 绝对不能保证实际上会。 也没有任何保证,如果调用的话,将以正确的顺序,正确的次数使用正确的参数。
In practice, this obviously hasn't been fatal: We've written callbacks for twenty years without breaking the Internet. And, in this case, we know that it's probably safe to hand control to core Node code.
在实践中,这显然不是致命的:我们编写回调已有20年,而没有中断Internet。 而且,在这种情况下,我们知道将控制权交给核心Node代码可能是安全的。
But handing control over mission-critical aspects of your application to a third party should feel risky, and has been the source of many a hard-to-squash heisenbug in the past.
但是,将应用程序的关键任务方面的控制交给第三方应该感到冒险,并且在过去,这已经成为许多难以破解的heisenbug的来源。
隐式错误处理 (Implicit Error Handling)
In synchronous code, we can use try
/catch
/finally
to handle errors.
在同步代码中,我们可以使用try
/ catch
/ finally
处理错误。
"use strict";
// This example uses Node, and so won't run in the browser.
const filename = 'text.txt',
fs = require('fs');
console.log('Reading file . . . ');
let file;
try {
// Wrong filename. D'oh!
file = fs.readFileSync(`${__dirname}/${filename + 'a'}`);
console.log( `Got it. Contents are: '${file}'` );
} catch (err) {
console.log( `There was a/n ${err}: file is ${file}` );
}
console.log( 'Catching errors, like a bo$$.' );
Async code lovingly tosses that out the window.
异步代码将其丢到窗外。
"use strict";
// This example uses Node, and so won't run in the browser.
const filename = 'throwaway.txt',
fs = require('fs');
console.log('Reading file . . . ');
let file;
try {
// Wrong filename. D'oh!
fs.readFile(`${__dirname}/${filename + 'a'}`, function (err, contents) {
file = contents;
});
// This shouldn't run if file is undefined
console.log( `Got it. Contents are: '${file}'` );
} catch (err) {
// In this case, catch should run, but it never will.
// This is because readFile passes errors to the callback -- it does /not/
// throw them.
console.log( `There was a/n ${err}: file is ${file}` );
}
This doesn't work as expected. This is because the try
block wraps readFile
, which will always return successfully with undefined
. This means that try
will always complete without incident.
这不符合预期。 这是因为try
块包装了readFile
, 它将始终成功返回undefined
。 这意味着try
将始终完成而不会发生任何事件。
The only way for readFile
to notify you of errors is to pass them to your callback, where we handle them ourselves.
readFile
通知您错误的唯一方法是将它们传递给回调函数,由我们自己处理。
"use strict";
// This example uses Node, and so won't run in the browser.
const filename = 'throwaway.txt',
fs = require('fs');
console.log('Reading file . . . ');
fs.readFile(`${__dirname}/${filename + 'a'}`, function (err, contents) {
if (err) { // catch
console.log( `There was a/n ${err}.` );
} else { // try
console.log( `Got it. File contents are: '${file}'`);
}
});
This example isn't so bad, but propagating information about the error through large programs quickly beomes unwieldly.
这个例子还不错,但是通过大型程序传播有关错误的信息很快就变得毫无用处了。
Promises address both of these problems, and several others, by uninverting control, and "synchronizing" our asynchronous code so as to enable more familiar error handling.
承诺解决这两个问题,和其他几个人,由联合国逆变控制,而“同步”我们的异步代码,以便能够更熟悉的错误处理。
承诺 ( Promises )
Imagine you just ordered the entire You Don't Know JS catalog from O'Reilly. In exchange for your hard-earned cash, they send a receipt acknowledging that you'll receive a shiny new stack of books next Monday. Until then, you don't have that new stack of books. But you can trust that you will, because they promised to send it.
想象一下,您刚刚从O'Reilly订购了整个“ 您不知道JS”目录。 为了换取您来之不易的现金,他们会发送一张收据,确认您将在下周一收到一叠崭新的书。 在那之前,你没有的书籍,新的堆栈。 但是你可以相信你会的,因为他们答应送它。
That promise is enough that, before they even arrive, you can plan to set aside time to read every day; agree to loan a few of the titles out to friends; and give your boss notice that you'll be too busy reading for a full week to come to the office. You don't need the books to make those plans—you just need to know you'll get them.
这个承诺已经足够,您甚至可以计划在每天阅读之前预留时间。 同意将一些头衔借给朋友; 并通知您的老板,您整整一个星期都忙于阅读,无法上班。 您不需要制定这些计划的书,您只需要知道您会得到它们。
Of course, O'Reilly might tell you a few days later that they can't fill the order for whatever reason. At that point, you'll erase that block of daily reading time; let your friends down know the you won't receive the books, after all; and tell your boss you actually will be reporting to work next week.
当然,几天后,O'Reilly可能会告诉您,无论出于何种原因,他们都无法履行订单。 届时,您将消除每天的阅读时间; 毕竟,让您的朋友知道您将不会收到这些书; 并告诉您的老板您实际上将在下周报告工作。
A promise is like that receipt. It's an object that stands in for a value that is not ready yet, but will be ready later—in other words, a future value. You treat the promise as if it were the value you're waiting for, and write your code as if you already had it.
一个承诺就像那张收据。 这是一个代表尚未准备好但将在以后 准备好的值(即, 将来的值)的对象 。 您将承诺视为您正在等待的值,然后将代码编写为已拥有。
In the event there's a hiccup, Promises handle the interrupted control flow internally, and allow you to use a special catch
keyword to handle errors. It's a little different from the synchronous version, but nonetheless more familiar than coordinating multiple error handlers across otherwise uncoordinated callbacks.
万一出现打h,Promises会在内部处理中断的控制流,并允许您使用特殊的catch
关键字来处理错误。 它与同步版本略有不同,但是比在不协调的回调中协调多个错误处理程序更为熟悉。
And, since a promise hands you the value when it's ready, you decide what to do with it. This fixes the inversion of control problem: You handle your application logic directly, without having to hand control to third parties.
而且,由于承诺在准备就绪时会为您带来价值,因此您可以决定如何处理它。 这解决了控制问题的反转: 您可以直接处理应用程序逻辑,而不必将控制权交给第三方。
承诺的生命周期:状态简述 (The Promise Life Cycle: A Brief Look at States)
Imagine you've used a Promise to make an API call.
假设您使用Promise进行API调用。
Since the server can't respond instantaneously, the Promise doesn't immediately contain its final value, nor will it be able to immediately report an error. Such a Promise is said to be pending. This is the case where you're waiting for your stack of books.
由于服务器无法即时响应,因此Promise不会立即包含其最终值,也无法立即报告错误。 据说这样的承诺尚未完成 。 在这种情况下,您正在等待一stack书。
Once the server does respond, there are two possible outcomes.
一旦服务器作出响应,有两种可能的结果。
- The Promise gets the value it expected, in which case it is fulfilled. This is receiving your book order. Promise获得了期望的价值,在这种情况下就实现了 。 这正在收到您的预订单。
- In the event there's an error somewhere along the pipeline, the Promise is said to be rejected. This is the notification that you won't get your order. 如果管道中某处出现错误,则Promise会被拒绝 。 这是关于您将无法获得订单的通知。
Together, these are the three possible states a Promise can be in. Once a Promise is either fulfilled or rejected, it cannot transition to any other state.
总之,这是一个Promise可能处于的三个状态 。一旦Promise被实现或被拒绝,它就不能转换到任何其他状态。
Now that the jargon is out of the way, let's see how we actually use these things.
现在,行话已成问题,让我们看看我们如何实际使用这些东西。
承诺的基本方法 ( Fundamental Methods on Promises )
To quote the Promises/A+ spec:
引用Promises / A +规范 :
A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its
then
method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.一个promise表示异步操作的最终结果。 与承诺进行交互的主要方式是通过其
then
方法,该方法注册回调以接收承诺的最终值或无法实现承诺的原因。
This section will take a closer look at the basic usage of Promises:
本节将仔细研究Promises的基本用法:
- Creating Promises with the constructor; 用构造函数创建Promises;
- Handling success with
resolve
; 有resolve
处理成功; - Handling errors with
reject
; and 通过reject
处理错误; 和 - Setting up control flow with
then
andcatch
.then
建立控制流程并catch
。
In this example, we'll use Promises to clean up the fs.readFile
code from above.
在此示例中,我们将使用Promises从上方清除fs.readFile
代码。
创造承诺 ( Creating Promises )
The most basic way to create a Promise is to use the constructor directly.
创建Promise的最基本方法是直接使用构造函数。
'use strict';
const fs = require('fs');
const text =
new Promise(function (resolve, reject) {
// Does nothing
})
Note that we pass the Promise constructor a function as an argument. This is where we tell the Promise how to execute the asynchronous operation; what to do when we get the value we expect; and what to do if we get an error. In particular:
请注意,我们将Promise构造函数传递给函数作为参数。 在这里,我们告诉Promise 如何执行异步操作; 当我们获得期望的价值时该怎么做; 以及出现错误时该怎么办。 特别是:
- The
resolve
argument is also a function, and encapsulates what we want to do when we receive the expected value. When we get that expected value (val
), we callresolve
with it:resolve(val)
.resolve
参数也是一个函数,它封装了当我们收到期望值时想要做的事情。 当我们得到的预期值(val
),我们称之为resolve
吧:resolve(val)
- The
reject
argument is also a function, and represents what we want to do when we receive an error. If we get an error (err
), we callreject
with it:reject(err)
.reject
参数也是一个函数,表示当我们收到错误时我们想做什么。 如果我们得到一个错误(err
),我们称之为reject
它:reject(err)
- Finally, the function we pass to the Promise constructor handles the asynchronous code itself. If it returns as expected, we call
resolve
with the value we get back. If it throws an error, we callreject
with the error. 最后,我们传递给Promise构造函数的函数将处理异步代码本身。 如果它按预期返回,则使用返回的值调用resolve
。 如果抛出错误,我们称reject
为错误。
Our running example is to wrap fs.readFile
in a Promise. What should our resolve
and reject
look like?
我们正在运行的示例是将fs.readFile
包装在Promise中。 我们的resolve
和reject
应该是什么样?
- In the event of success, we want to
console.log
the file contents. 如果成功,我们要console.log
文件内容。 - In the event of error, we'll do the same thing:
console.log
the error. 如果发生错误,我们将做同样的事情:console.log
错误。
That nets us something like this.
这使我们得到了类似的东西。
// constructor.js
const resolve = console.log,
reject = console.log;
Next, we need to fill out the function that we pass to the constructor. Remember, our task is to:
接下来,我们需要填写传递给构造函数的函数。 请记住,我们的任务是:
- Read a file, and 读取文件,然后
- If successful,
resolve
the contents; 如果成功,则resolve
内容; - Else,
reject
with an error. 否则,以错误reject
。
Thus:
从而:
// constructor.js
const text =
new Promise(function (resolve, reject) {
// Normal fs.readFile call, but inside Promise constructor . . .
fs.readFile('text.txt', function (err, text) {
// . . . Call reject if there's an error . . .
if (err)
reject(err);
// . . . And call resolve otherwise.
else
// We need toString() because fs.readFile returns a buffer.
resolve(text.toString());
})
})
With that, we're technically done: This code creates a Promise that does exactly what we want it to. But, if you run the code, you'll notice that it executes without printing a result or an error.
到此,我们在技术上就完成了:这段代码创建了一个Promise,可以完全实现我们想要的功能。 但是,如果您运行代码,则会注意到它在执行时没有打印结果或错误。
她答应了,然后。 。 。 ( She made a Promise, and then . . . )
The problem is that we wrote our resolve
and reject
methods, but didn't actually pass them to the Promise! For that, we need to introduce the basic function for setting up Promise-based control-flow: then
.
问题在于我们编写了我们的resolve
方法和reject
方法,但实际上并没有将它们传递给Promise! 为此,我们需要介绍用于设置基于Promise的控制流的基本功能: then
。
Every Promise has a method, called then
, which accepts two functions as arguments: resolve
, and reject
, in that order. Calling then
on a Promise and passing it these functions allows the function you passed to the constructor to access them.
每个Promise都有一个名为then
的方法,该方法接受两个函数作为参数: resolve
和reject
顺序 。 then
在Promise上调用并传递这些函数,使您传递给构造函数的函数可以访问它们。
// constructor.js
const text =
new Promise(function (resolve, reject) {
fs.readFile('text.txt', function (err, text) {
if (err)
reject(err);
else
resolve(text.toString());
})
})
.then(resolve, reject);
With that, our Promise reads the file, and calls the resolve
method we wrote before upon success.
这样,我们的Promise将读取文件,并在成功后调用我们之前编写的resolve
方法。
It's also crucial to remember that then
always returns a Promise object. That means you can chain several then
calls to create complex and synchronous-looking control flows over asynchronous operations. We'll dig into this in much more detail in the next installment, but the catch
example in the next subsection gives a taste as to what this looks like.
同样重要的是要记住, then
总是返回Promise对象 。 这意味着您可以将多个then
调用链接起来,以在异步操作上创建复杂的,看起来很同步的控制流。 在下一部分中,我们将对此进行更详细的研究,但是下一节中的catch
示例将使您对它的外观有所了解。
用于捕获错误的语法糖 ( Syntactical Sugar for Catching Errors )
We passed then
two functions: resolve
, which we call in the event of success; and reject
, which we call in the event of error.
then
我们传递了两个函数: resolve
,成功时调用; 并reject
,如果发生错误,我们称之为。
Promises also expose a function similar to then
, called catch
. It accepts a reject handler as its single argument.
承诺也暴露出类似的功能then
,叫catch
。 它接受拒绝处理程序作为其单个参数。
Since then
always returns a Promise, in the example above, we could have only passed then
a resolve handler, and chained a catch
with our reject handler afterwards.
由于then
总是返回一个承诺,在上面的例子中,我们可以唯一通过then
下决心处理程序,并链接一个catch
我们的拒绝之后的处理程序。
const text =
new Promise(function (resolve, reject) {
fs.readFile('tex.txt', function (err, text) {
if (err)
reject(err);
else
resolve(text.toString());
})
})
.then(resolve)
.catch(reject);
Finally, it's worth pointing out that catch(reject)
is just syntactic sugar for then(undefined, reject)
. So, we could also write:
最后,值得指出的是catch(reject)
只是then(undefined, reject)
语法糖。 因此,我们也可以这样写:
const text =
new Promise(function (resolve, reject) {
fs.readFile('tex.txt', function (err, text) {
if (err)
reject(err);
else
resolve(text.toString());
})
})
.then(resolve)
.then(undefined, reject);
. . . But that's much less readable.
。 。 。 但这很难理解。
结语 ( Wrapping Up )
Promises are an indispensable tool in the async programming toolkit. They can be intimidating at first, but that's only because they're unfamiliar: Use them a few times, and they'll be as natural as if
/else
.
承诺是异步编程工具包中必不可少的工具。 他们可以在第一吓人,但这只是因为他们不熟悉的:他们使用了几次,他们会是一样自然if
/ else
。
Next time, we'll get some practice by converting callback-based code to use Promises, and take a look at Q, a popular Promises library.
下次,我们将通过转换基于回调的代码以使用Promises进行一些练习,并看一看Q (流行的Promises库)。
Until then, read Domenic Denicola's States and Fates to master the terminology, and read Kyle Simpson's chapter on Promises from the book series we ordered earlier.
在此之前,请阅读Domenic Denicola的《状态和命运》以掌握术语,并从我们先前订购的丛书中阅读Kyle Simpson关于应许的章节。
As always, drop questions in the comments below, or shoot them to me on Twitter (@PelekeS). I promise to respond!
与往常一样,在下面的评论中提问,或在Twitter( @PelekeS )上向我射击。 我保证会回应!
翻译自: https://scotch.io/tutorials/understanding-javascript-promises-pt-i-background-basics