我与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拍手是有原因的
2016年学习JavaScript的感觉
在撰写本文期间,没有创建JavaScript框架。 hackernoon.com
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 =新的Promise(function(resolve,reject){
如果(keepsHisWord){
resolve(“男人喜欢守口如瓶”);
}其他{
拒绝(“那个人不想遵守他的话”);
}
});
console.log(promise1);

由于此诺言已得到立即解决,因此我们将无法检查诺言的初始状态。 因此,让我们创建一个新的承诺,这将需要一些时间来解决。 最简单的方法是使用setTimeOut函数。
promise2 =新的Promise(function(resolve,reject){
setTimeout(function(){
解决({
信息:“这个男人喜欢守口如瓶”,
代码:“ aManKeepsHisWord”
});
},10 * 1000);
});
console.log(promise2);
上面的代码只是创建了一个承诺,它将在10秒后无条件解决。 因此,我们可以检查诺言的状态,直到兑现为止。

一旦十秒过去,诺言就解决了。 PromiseStatus和PromiseValue更新。 如您所见,我们更新了resolve函数,以便我们可以传递JSON Object而不是简单的字符串。 这只是为了表明我们也可以在resolve函数中传递其他值。

现在让我们来看一个将被拒绝的诺言。 让我们为此稍微修改promise 1。
keepsHisWord = false;
promise3 =新的Promise(function(resolve,reject){
如果(keepsHisWord){
resolve(“男人喜欢守口如瓶”);
}其他{
拒绝(“那个男人不想遵守他的诺言”);
}
});
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了诺言时,将运行以下三种方法中的一种或多种。
Promise.prototype.catch(onRejected)
Promise.prototype.then(onFulfilled, onRejected)
Promise.prototype.finally(onFinally)
下图显示了.catch和.catch方法的流程。 由于它们返回了Promise因此可以再次链接它们,这也显示在图像中。 如果.finally被声明为一个承诺,那么无论何时履行承诺,无论履行还是拒绝,都会执行它。 正如康斯坦丁·鲁达(Konstantin Rouda)所指出的,对“最终”的支持有限,因此请在使用前进行检查。

这是一个小故事。 您是一个正在上学的孩子,您问妈妈电话。 她说:“我将在本月底购买一部电话。”
让我们看看如果在月底执行了Promise,它将在JavaScript中的外观如何。
var momsPromise =新的Promise(function(resolve,reject){
momsSavings = 20000;
priceOfPhone = 60000;
如果(momsSavings> priceOfPhone){
解决({
品牌:“ iphone”,
型号:“ 6s”
});
}其他{
拒绝(“我们没有足够的储蓄。让我们多储蓄一些。”);
}
});
momsPromise.then(function(value){
console.log(“万岁,我把这部手机作为礼物”,JSON.stringify(value));
});
momsPromise.catch(function(reason){
console.log(“妈妈不能给我买电话,因为”,原因);
});
momsPromise.finally(function(){
console.log(
“不管我妈妈是否可以给我买电话,我仍然爱她”
);
});
输出将是。

如果我们将妈妈momsSavings的价值momsSavings为200000,则妈妈将能够为儿子送礼。 在这种情况下,输出将是

让我们戴上消耗这个图书馆的人的帽子。 我们正在模拟输出和性质,以便我们可以研究如何使用然后有效捕获。
由于onFulfilled, onRejected handlers可以同时分配onFulfilled, onRejected handlers ,而不是编写单独的.catch和.catch .then .catch我们可以对.catch进行相同的处理。
momsPromise.then(
函数(值){
console.log(“万岁,我把这部手机作为礼物”,JSON.stringify(value));
},
功能(原因){
console.log(“妈妈不能给我买电话,因为”,原因);
}
);
但是为了代码的可读性,我认为最好将它们分开。
为了确保我们可以在常规浏览器或特定于chrome的浏览器中运行所有这些示例,请确保我们的代码示例中没有外部依赖项。 为了更好地理解其他主题,让我们创建一个函数,该函数将返回将被随机解决或拒绝的promise,以便我们可以测试各种情况。 为了理解异步函数的概念,让我们在函数中引入一个随机延迟。 由于我们将需要随机数,因此让我们首先创建一个随机函数,该函数将返回x和y之间的随机数。
函数getRandomNumber(开始= 1,结束= 10){
//当start,end都> = 1并且end> start时工作
返回parseInt(Math.random()* end)%(end-start + 1)+开始;
}
让我们创建一个函数,该函数将为我们返回承诺。 让我们调用函数promiseTRRARNOSG ,它是promiseTRRARNOSG的别名。 此函数将创建一个承诺,该承诺将在2到10之间的随机秒数后解析或拒绝。要随机化拒绝和解析,我们将在1到10之间创建一个随机数。如果生成的随机数大于5,我们将解析答应,否则我们将拒绝。
函数getRandomNumber(开始= 1,结束= 10){
//当开始和结束都> = 1时工作
返回(parseInt(Math.random()*结束)%(结束-开始+ 1))+开始;
}
var promiseTRRARNOSG =(promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator= function(){
返回新的Promise(function(resolve,reject){
让randomNumberOfSeconds = getRandomNumber(2,10);
setTimeout(function(){
让randomiseResolving = getRandomNumber(1,10);
如果(randomiseResolving> 5){
解决({
randomNumberOfSeconds:randomNumberOfSeconds,
randomiseResolving:randomiseResolving
});
}其他{
拒绝({
randomNumberOfSeconds:randomNumberOfSeconds,
randomiseResolving:randomiseResolving
});
}
},randomNumberOfSeconds * 1000);
});
});
var testProimse = promiseTRRARNOSG();
testProimse.then(function(value){
console.log(“ promise被解决时的值:”,value);
});
testProimse.catch(function(reason){
console.log(“拒绝承诺时的原因:”,原因);
});
//让我们循环浏览并使用该函数创建十个不同的Promise,以查看一些变化。 有些将得到解决,有些将被拒绝。
对于(i = 1; i <= 10; i ++){
让promise = promiseTRRARNOSG();
promise.then(function(value){
console.log(“ promise被解决时的值:”,value);
});
promise.catch(function(reason){
console.log(“拒绝承诺时的原因:”,原因);
});
}
刷新浏览器页面并在控制台中运行代码以查看用于resolve方案和reject方案的不同输出。 展望未来,我们将看到我们如何创建多个promise并检查其输出,而无需执行此操作。
静态方法
Promise对象中有四种静态方法。
前两个是助手方法或快捷方式。 它们可帮助您轻松创建已解决或已拒绝的承诺。
Promise.reject(reason)
帮助您创建被拒绝的承诺。
var promise3 = Promise.reject(“不感兴趣”);
promise3.then(function(value){
console.log(“这将不会运行,因为它是已解决的承诺。已解决的值为”,value);
});
promise3.catch(function(reason){
console.log(“此运行是因为它被拒绝了。原因是”,reason);
});
Promise.resolve(value)
帮助您创建已解决的承诺。
var promise4 = Promise.resolve(1);
promise4.then(函数(值){
console.log(“这将作为重新约定的诺言运行。解析的值为”,value);
});
promise4.catch(功能(原因){
console.log(“这将不会运行,因为它是已解决的承诺”,原因);
});
在旁注中,promise可以具有多个处理程序。 因此,您可以将上面的代码更新为
var promise4 = Promise.resolve(1);
promise4.then(函数(值){
console.log(“这将作为重新约定的诺言运行。解析的值为”,value);
});
promise4.then(函数(值){
console.log(“这还将在添加多个处理程序的情况下运行。打印出两倍的解析值”,value * 2);
});
promise4.catch(功能(原因){
console.log(“这将不会运行,因为它是已解决的承诺”,原因);
});
和输出将看起来像。

接下来的两种方法可帮助您处理一组承诺。 当您处理多个promise时,最好先创建一个promise数组,然后对promise集合进行必要的操作。 为了理解这些方法,我们将无法使用我们方便的promiseTRRARNOSG因为它太随机了。 最好有一些确定性的承诺,以便我们能够理解其行为。 让我们创建两个函数。 一种将在n秒后解决,另一种将在n秒后拒绝。
var promiseTRSANSG =(promiseThatResolvesAfterNSecondsGenerator = function(
n = 0
){
返回新的Promise(function(resolve,reject){
setTimeout(function(){
解决({
NSAftersconds:n
});
},n * 1000);
});
});
var promiseTRJANSG =(promiseThatRejectsAfterNSecondsGenerator = function(
n = 0
){
返回新的Promise(function(resolve,reject){
setTimeout(function(){
拒绝({
NSEconds后拒绝: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(“所有的诺言都实现了”,值);
});
handleAllPromises.catch(function(reason){
console.log(“一个承诺由于以下原因而失败”,原因);
});

我们通常需要从输出中得出两个重要的观察结果。
第一:花费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(“所有的诺言都实现了”,值);
});
handleAllPromises.catch(function(reason){
console.log(“一个承诺由于以下原因而失败”,原因);
});

由于数组中没有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(“所有的诺言都实现了”,值);
});
handleAllPromises.catch(function(reason){
console.timeEnd(“ Promise.All”);
console.log(“其中一个承诺失败,原因如下,”,原因);
});

无极种族
根据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(“禁食承诺已解决”,值);
});
promisesRace.catch(function(reason){
console.timeEnd(“ Promise.race”);
console.log(“最快的承诺由于以下原因而被拒绝,原因”;
});

所有的诺言都是并行进行的。 第三个承诺在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(“禁食承诺已解决”,值);
});
promisesRace.catch(function(reason){
console.timeEnd(“ Promise.race”);
console.log(“最快的承诺由于以下原因而被拒绝,原因”;
});

所有的诺言都是并行进行的。 第四个承诺在3秒内被拒绝。 完成此操作后, Promise.race返回的承诺将被拒绝。
我已经编写了所有示例方法,以便可以测试各种场景,并且可以在浏览器本身中运行测试。 这就是在示例中看不到任何API调用,文件操作或数据库调用的原因。 尽管所有这些都是真实的示例,但是您需要付出更多的努力来设置它们并进行测试。 而使用延迟功能可为您提供类似的方案,而无需额外设置。 您可以轻松地使用这些值来查看和签出不同的方案。 您可以结合使用promiseTRJANSG , promiseTRSANSG和promiseTRRARNOSG方法来模拟足够的场景,以全面了解promiseTRRARNOSG 。 在相关块之前和之后也使用console.time方法将有助于我们轻松确定诺言是并行运行还是顺序运行。 让我知道您是否还有其他有趣的情况,或者我错过了什么。 如果您想将所有代码示例放在一个地方,请查看此要点。
蓝鸟具有一些有趣的功能,例如
- Promise.prototype.timeout
- 承诺
- 承诺
我们将在另一篇文章中讨论这些内容。
我还将再写一篇关于我从异步和等待中学习的文章。
在结束之前,我想列出我遵循的所有拇指法则,以使我的头脑保持对诺言的理智。
使用诺言的拇指规则
- 每当您使用异步或阻塞代码时,请使用Promise。
-
resolve地图thenreject所有实际用途的地图。 - 确保为所有promise编写
.catch和.catch方法。 - 如果在两种情况下都需要执行某些操作,请使用
.finally - 改变每个诺言我们只有一枪。
- 我们可以将多个处理程序添加到一个Promise中。
-
Promise对象中所有方法的返回类型(无论是静态方法还是原型方法)都是Promise - 在
Promise.all中,所有诺言的顺序都将保持在value变量中,而与首先解决哪个诺言Promise.all。