我与JavaScript有一种“爱与恨”的关系。 但是尽管如此,JavaScript一直吸引着我。 在过去的10年中从事Java和PHP的工作后,JavaScript看起来非常不同,但很有趣。 我没有花足够的时间在JavaScript上,并且一直试图弥补这一点。
承诺是我遇到的第一个有趣的话题。 我一次又一次听到人们说Promises使您从Callback地狱中解救出来。 尽管这可能是令人愉快的副作用,但Promises还有很多,而这是我到目前为止能够弄清的。 这将是一篇长篇文章,如果您想突出显示某些部分,可以使用我们的扩展名http://bit.ly/highlights-extension
背景
当您第一次开始使用JavaScript时,可能会有些沮丧。 您会听到有人说JavaScript是同步编程语言,而其他人则说它是异步的。 您会听到阻塞代码,非阻塞代码,事件驱动的设计模式,事件生命周期,函数堆栈,事件队列,冒泡,polyfill,babel,angular,reactJS,vue JS以及大量其他工具和库。 不用。 您不是第一个。 也有一个术语。 这称为JavaScript疲劳 。 该推文很好地捕捉了它。
如果您想了解有关JavaScript疲劳的更多详细信息,请查看以下文章。 这篇帖子在Hackernoon上获得42k拍手是有原因的:)
JavaScript是一种同步编程语言。 但是由于有了回调函数,我们可以使其像异步编程语言一样起作用。
外行的承诺
JavaScript中的承诺与您在现实生活中所做的承诺非常相似。 因此,首先让我们看看现实生活中的承诺。
字典中promise的定义如下
Promise :名词:确保某人会做某事或某件事会发生。
那么,当有人向你许诺时会发生什么呢?
- 一个承诺给您保证一定会做的事情。 他们(做出承诺)将自己完成还是由他人完成,这并不重要。 他们给您保证,您可以基于此计划。
- 一个诺言既可以兑现也可以兑现。
- 当信守诺言时,您会期望从诺言中得到一些收益。 您可以将promise的输出用于您的进一步操作或计划。
- 当诺言被破坏时,您想知道为什么诺言的人不能跟上他的承诺。 一旦知道原因并确认诺言已被兑现,您就可以计划下一步该怎么做或如何处理它。
- 在作出承诺时,我们所拥有的只是保证。 我们将无法立即采取行动。 当承诺兑现 (因此我们有预期的结果)或违约 (我们知道原因,因此我们可以计划应急计划)时,我们可以决定并制定需要做什么。
- 您有可能根本没有听到作出承诺的人的回音。 在这种情况下,您希望保留时间阈值。 假设做出承诺的人在10天内没有回到我身边,我会认为他遇到了一些问题,不会遵守诺言。 因此,即使对方在15天后回到您身边,也不再重要,因为您已经制定了替代计划。
JavaScript中的承诺
根据经验,对于JavaScript,我总是从MDN Web Docs阅读文档。 在所有资源中,我认为它们提供了最简洁的细节。 我从MDSN Web Docs中阅读了Promises页面,并玩弄了一些代码以了解它。
理解诺言有两个部分。 承诺的创建和承诺的 处理 。 尽管我们的大多数代码通常都可以满足其他库创建的诺言的处理,但是完全理解将对我们有一定帮助。 一旦您进入初学者阶段,对“创造承诺”的理解同样重要。
创造承诺
让我们看看创建新承诺的签名。
new Promise( /* executor */ function(resolve, reject) { ... } ); 构造函数接受一个称为执行程序的函数。 该executor函数接受两个参数resolve和reject ,这两个参数又是函数。 通常使用Promise来简化异步操作或阻塞代码的处理,例如文件操作,API调用,DB调用,IO调用等。这些异步操作的启动在executor函数中进行。 如果异步操作成功,则由promise的创建者调用resolve函数来返回预期结果。 同样,如果出现一些意外错误,则通过调用reject函数将原因继续传递。
现在我们知道了如何创建承诺。 为了我们的理解,让我们创建一个简单的承诺。
var keepsHisWord;
keepsHisWord = true;
promise1 = new Promise(function(resolve, reject) {
if (keepsHisWord) {
resolve("The man likes to keep his word");
} else {
reject("The man doesnt want to keep his word");
}
});
console.log(promise1);
由于此诺言已得到立即解决,因此我们将无法检查诺言的初始状态。 因此,让我们创建一个新的承诺,这将需要一些时间来解决。 最简单的方法是使用setTimeOut函数。
promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve({
message: "The man likes to keep his word",
code: "aManKeepsHisWord"
});
}, 10 * 1000);
});
console.log(promise2);上面的代码只是创建了一个承诺,它将在10秒后无条件解决。 因此,我们可以检查诺言的状态,直到兑现为止。
一旦十秒过去,诺言就解决了。 PromiseStatus和PromiseValue更新。 如您所见,我们更新了resolve函数,以便我们可以传递JSON Object而不是简单的字符串。 这只是为了表明我们也可以在resolve函数中传递其他值。
现在让我们来看一个将被拒绝的诺言。 让我们为此稍微修改promise 1。
keepsHisWord = false;
promise3 = new Promise(function(resolve, reject) {
if (keepsHisWord) {
resolve("The man likes to keep his word");
} else {
reject("The man doesn't want to keep his word");
}
});
console.log(promise3);
由于这将创建未经处理的拒绝,因此Chrome浏览器将显示错误。 您现在可以忽略它。 我们稍后再讲。
如我们所见, PromiseStatus可以具有三个不同的值。 pending resolved或已rejected pending创建承诺时, PromiseStatus将处于pending状态,并且在承诺被resolved或被rejected.之前, PromiseValue状态为undefined rejected. 当一个承诺处于已resolved或被rejected状态时,一个承诺被称为已settled. 因此,承诺通常会从待处理状态过渡到已解决状态。
现在我们知道了如何创建承诺,现在我们可以看看如何使用或处理承诺。 这将与理解Promise对象并驾齐驱。
了解承诺对象
根据MDN文档
Promise对象表示异步操作的最终完成(或失败)及其结果值。
Promise对象具有静态方法和prototype methods在静态方法Promise对象可以被独立地应用,而prototype methods上的实例被应用于需要Promise对象。 记住正常的方法和原型都返回Promise ,这使事情的理解变得容易得多。
原型方法
让我们首先从prototype methods开始。共有三种。 重申一下,所有这些方法都可以应用于Promise对象的实例,并且所有这些方法依次返回promise。 以下所有方法为诺言的不同状态转换分配处理程序。 正如我们之前看到的,创建Promise时,它处于pending状态。 当一个承诺是基于他们是否解决一个或多个以下三种方法将运行fulfilled或rejected 。
Promise.prototype.catch(onRejected)
Promise.prototype.then(onFulfilled, onRejected)
Promise.prototype.finally(onFinally)
下面的图像示出了用于流.then和.catch方法。 由于它们返回了Promise因此可以再次链接它们,这也显示在图像中。 如果.finally被宣布为一个承诺那么无论什么时候承诺是它将被执行settled其是否满足或拒绝无关。 正如康斯坦丁·鲁达(Konstantin Rouda)所指出的,对“最终”的支持有限,因此请在使用前进行检查。
这是一个小故事。 您是一个正在上学的孩子,您问妈妈电话。 她说:“我将在本月底购买一部电话。”
让我们看看如果在月底执行了Promise,它将在JavaScript中的外观如何。
var momsPromise = new Promise(function(resolve, reject) {
momsSavings = 20000;
priceOfPhone = 60000;
if (momsSavings > priceOfPhone) {
resolve({
brand: "iphone",
model: "6s"
});
} else {
reject("We donot have enough savings. Let us save some more money.");
}
});momsPromise.then(function(value) {
console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
});momsPromise.catch(function(reason) {
console.log("Mom coudn't buy me the phone because ", reason);
});momsPromise.finally(function() {
console.log(
"Irrespecitve of whether my mom can buy me a phone or not, I still love her"
);
});输出将是。
如果我们将妈妈momsSavings的价值momsSavings为200000,则妈妈将能够为儿子送礼。 在这种情况下,输出将是
让我们戴上消耗这个图书馆的人的帽子。 我们正在模拟输出和性质,以便我们可以研究如何使用然后有效捕获。
因为.then可以分配两个onFulfilled, onRejected handlers ,而不是单独的写.then和.catch我们可以做同样的用.then它看起来会像下面。
momsPromise.then(
function(value) {
console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
},
function(reason) {
console.log("Mom coudn't buy me the phone because ", reason);
}
);
但是为了代码的可读性,我认为最好将它们分开。
为了确保我们可以在常规浏览器或特定于chrome的浏览器中运行所有这些示例,请确保我们的代码示例中没有外部依赖项。 为了更好地理解其他主题,让我们创建一个函数,该函数将返回将被随机解决或拒绝的promise,以便我们可以测试各种情况。 为了理解异步函数的概念,让我们在函数中引入一个随机延迟。 由于我们将需要随机数,因此让我们首先创建一个随机函数,该函数将返回x和y之间的随机数。
function getRandomNumber(start = 1, end = 10) {
//works when both start,end are >=1 and end > start
return parseInt(Math.random() * end) % (end-start+1) + start;
} 让我们创建一个函数,该函数将为我们返回承诺。 让我们把我们的功能promiseTRRARNOSG这是一个别名promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator 。 此函数将创建一个承诺,该承诺将在2到10之间的随机秒数后解析或拒绝。要对拒绝和解析进行随机化,我们将在1到10之间创建一个随机数。如果生成的随机数大于5,我们将解析答应,否则我们将拒绝。
function getRandomNumber(start = 1, end = 10) {
//works when both start and end are >=1
return (parseInt(Math.random() * end) % (end - start + 1)) + start;
}promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator
return new Promise(function(resolve, reject) {
let randomNumberOfSeconds = getRandomNumber(2, 10);
setTimeout(function() {
let randomiseResolving = getRandomNumber(1, 10);
if (randomiseResolving > 5) {
resolve({
randomNumberOfSeconds: randomNumberOfSeconds,
randomiseResolving: randomiseResolving
});
} else {
reject({
randomNumberOfSeconds: randomNumberOfSeconds,
randomiseResolving: randomiseResolving
});
}
}, randomNumberOfSeconds * 1000);
});
}); var testProimse = promiseTRRARNOSG();
testProimse.then(function(value) {
console.log("Value when promise is resolved : ", value);
});
testProimse.catch(function(reason) {
console.log("Reason when promise is rejected : ", reason);
});
// Let us loop through and create ten different promises using the function to see some variation. Some will be resolved and some will be rejected.
for (i=1; i<=10; i++) {
let promise = promiseTRRARNOSG();
promise.then(function(value) {
console.log("Value when promise is resolved : ", value);
});
promise.catch(function(reason) {
console.log("Reason when promise is rejected : ", reason);
});
} 刷新浏览器页面并在控制台中运行代码以查看用于resolve方案和reject方案的不同输出。 展望未来,我们将看到我们如何创建多个promise并检查其输出,而无需执行此操作。
静态方法
Promise对象中有四种静态方法。
前两个是助手方法或快捷方式。 它们可帮助您轻松创建已解决或已拒绝的承诺。
Promise.reject(reason)
帮助您创建被拒绝的承诺。
var promise3 = Promise.reject("Not interested");
promise3.then(function(value){
console.log("This will not run as it is a resolved promise. The resolved value is ", value);
});
promise3.catch(function(reason){
console.log("This run as it is a rejected promise. The reason is ", reason);
}); Promise.resolve(value)
帮助您创建已解决的承诺。
var promise4 = Promise.resolve(1);
promise4.then(function(value){
console.log("This will run as it is a resovled promise. The resolved value is ", value);
});
promise4.catch(function(reason){
console.log("This will not run as it is a resolved promise", reason);
});
在旁注中,promise可以具有多个处理程序。 因此,您可以将上面的代码更新为
var promise4 = Promise.resolve(1);
promise4.then(function(value){
console.log("This will run as it is a resovled promise. The resolved value is ", value);
});
promise4.then(function(value){
console.log("This will also run as multiple handlers can be added. Printing twice the resolved value which is ", value * 2);
});
promise4.catch(function(reason){
console.log("This will not run as it is a resolved promise", reason);
});
和输出将看起来像。
接下来的两种方法可帮助您处理一组承诺。 当您处理多个promise时,最好先创建一个promise数组,然后对promise集合进行必要的操作。 为了理解这些方法,我们将无法使用我们方便的promiseTRRARNOSG因为它太随机了。 最好有一些确定性的承诺,以便我们能够理解其行为。 让我们创建两个函数。 一种将在n秒后解决,另一种将在n秒后拒绝。
var promiseTRSANSG = (promiseThatResolvesAfterNSecondsGenerator = function(
n = 0
) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve({
resolvedAfterNSeconds: n
});
}, n * 1000);
});
});
var promiseTRJANSG = (promiseThatRejectsAfterNSecondsGenerator = function(
n = 0
) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject({
rejectedAfterNSeconds: n
});
}, n * 1000);
});
});
现在让我们使用这些帮助器功能来了解Promise.All
无极
根据MDN文档
Promise.all(iterable)方法返回一个Promise,当iterable参数中的所有promise已解决或可迭代参数不包含promise时,该Promise进行解析。 它以第一个承诺被拒绝的理由拒绝。
情况1 :当所有的诺言都实现了。 这是最常用的方案。
console.time("Promise.All");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(1));
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(2));
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
console.timeEnd("Promise.All");
console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
console.log("One of the promises failed with the following reason", reason);
});
我们通常需要从输出中得出两个重要的观察结果。
第一:花费2秒的第三个诺言完成,而花费4秒的第二个诺言完成。 但是,正如您在输出中看到的那样,承诺的顺序将保留在值中。
第二:我添加了一个控制台计时器,以找出Promise.All需要多长时间。 如果按顺序执行承诺,则总共应花费1 + 4 + 2 = 7秒。 但是从我们的计时器中我们看到只需要4秒。 这证明了所有诺言都是并行执行的。
情况2:没有承诺时。 我认为这是最不常用的。
console.time("Promise.All");
var promisesArray = [];
promisesArray.push(1);
promisesArray.push(4);
promisesArray.push(2);
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
console.timeEnd("Promise.All");
console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
console.log("One of the promises failed with the following reason", reason);
});
由于数组中没有promise,因此将解决返回的promise。
情况3:以第一个承诺被拒绝的原因拒绝。
console.time("Promise.All");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(1));
promisesArray.push(promiseTRSANSG(5));
promisesArray.push(promiseTRSANSG(3));
promisesArray.push(promiseTRJANSG(2));
promisesArray.push(promiseTRSANSG(4));
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
console.timeEnd("Promise.All");
console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
console.timeEnd("Promise.All");
console.log("One of the promises failed with the following reason ", reason);
});
无极种族
根据MDN文档
Promise.race(iterable)方法将返回一个可解决或拒绝的promise,该promise中的一个promise之一会解析或拒绝,并带有该promise的值或原因。
案例1:其中一个承诺首先解决。
console.time("Promise.race");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(3));
promisesArray.push(promiseTRSANSG(2));
promisesArray.push(promiseTRJANSG(3));
promisesArray.push(promiseTRSANSG(4));
var promisesRace = Promise.race(promisesArray);
promisesRace.then(function(values) {
console.timeEnd("Promise.race");
console.log("The fasted promise resolved", values);
});
promisesRace.catch(function(reason) {
console.timeEnd("Promise.race");
console.log("The fastest promise rejected with the following reason ", reason);
});
所有的诺言都是并行进行的。 第三个承诺在2秒内解决。 一旦完成, Promise.race返回的Promise.race被解决。
情况2:其中一个承诺首先被拒绝。
console.time("Promise.race");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(6));
promisesArray.push(promiseTRSANSG(5));
promisesArray.push(promiseTRJANSG(3));
promisesArray.push(promiseTRSANSG(4));
var promisesRace = Promise.race(promisesArray);
promisesRace.then(function(values) {
console.timeEnd("Promise.race");
console.log("The fasted promise resolved", values);
});
promisesRace.catch(function(reason) {
console.timeEnd("Promise.race");
console.log("The fastest promise rejected with the following reason ", reason);
});
所有的诺言都是并行进行的。 第四个承诺在3秒内被拒绝。 完成此操作后, Promise.race返回的承诺将被拒绝。
我已经编写了所有示例方法,以便可以测试各种场景,并且可以在浏览器本身中运行测试。 这就是在示例中看不到任何API调用,文件操作或数据库调用的原因。 尽管所有这些都是真实的示例,但是您需要付出更多的努力来设置它们并进行测试。 而使用延迟功能可为您提供类似的方案,而无需额外设置。 您可以轻松地使用这些值来查看和签出不同的方案。 您可以结合使用promiseTRJANSG , promiseTRSANSG和promiseTRRARNOSG方法来模拟足够的场景,以全面了解promiseTRRARNOSG 。 在相关块之前和之后也使用console.time方法将有助于我们轻松确定诺言是并行运行还是顺序运行。 让我知道您是否还有其他有趣的情况,或者我错过了什么。 如果您想将所有代码示例放在一个地方,请查看此要点。
蓝鸟具有一些有趣的功能,例如
- Promise.prototype.timeout
- 承诺
- 承诺
我们将在另一篇文章中讨论这些内容。
我还将再写一篇关于我从异步和等待中学习的文章。
在结束之前,我想列出我遵循的所有拇指法则,以使我的头脑保持对诺言的理智。
使用诺言的拇指规则
- 每当您使用异步或阻塞代码时,请使用Promise。
-
resolve映射到then和reject映射到catch所有的实际目的。 - 确保写入两根
.catch和.then对所有承诺的方法。 - 如果在两种情况下都需要执行某些操作,请使用
.finally - 改变每个诺言我们只有一枪。
- 我们可以将多个处理程序添加到一个Promise中。
-
Promise对象中所有方法的返回类型(无论是静态方法还是原型方法)都是Promise - 在
Promise.all中,所有诺言的顺序都将保持在value变量中,而与首先解决哪个诺言Promise.all。
请指出我是否在这里遗漏了一些东西,或者可以改善一些地方。 当我们在MERN堆栈上构建当前产品https://learningpaths.io/时,我开始花更多的时间在Javascript上。 我花了几天的时间在Google上搜索和阅读有关诺言的信息。 跟踪链接和注释真是一团糟。 因此,我们着手构建一个chrome扩展程序,该扩展程序将完全解决此问题并帮助您组织学习路径。 它仍处于Alpha阶段。 我们将与选定的100个人分享它,他们希望尝试一下并向我们提供反馈。 如果您有兴趣,请访问https://learningpaths.io/,并告诉我们您有兴趣获得早期访问权。 如果您认为您的朋友或同事有兴趣,请传播。
如果您喜欢这篇文章并想阅读类似的文章,请别忘了鼓掌。
理解系列
本文是我的学习系列(称为“ 理解X”)的一部分 。 您可以在此链接中阅读更多内容。
了解Javascript
了解Linux
了解React
了解学习
了解加密货币
From: https://hackernoon.com/understanding-promises-in-javascript-13d99df067c1