理解JS Promises的简单指南

老实说 刚开始学习诺言时,我很难理解诺言。 我必须遍历多篇文章和示例,并进行大量实验以弄清发生了什么。 我觉得我读过的大多数解释都给读者留下了太多的印象,并没有赋予任何复杂想法所要求的正义:将其分解成可以理解的小部分。

promises的目的是通过消除过度使用回调函数来编写更简洁的代码。 如果您花了很多时间在JavaScript上,那么您可能会熟悉我们称为“回调地狱”的东西,它始于您传递大量功能,最后您对导致创建JavaScript的每个决定都感叹不已。 只需问这个人Max Ogden -他显然对回调地狱感到不高兴,以至于他创建了一个有关该网站的网站:callbackhell.com。

我们当中确实有一个精明的精英,他们只需要使用M诺德翻阅MDN页面,然后他们就立即编写基于诺基的代码。 我显然不是其中之一,如果您像我一样,请继续阅读。 我保证您会对诺言有个很好的了解。

从诺言开始

简单来说,promise是代表操作对象 。 通常,promise用于表示异步操作,例如AJAX调用或动画。

由于以上两个句子对提高我们对Promise的理解绝对没有任何作用,因此让我们尝试使用一个示例使用Promise对象来表示一个非常简单的操作:

 函数doLog(){ 
console.log('Hello World!');
}

doLog是一个函数,而函数是操作。 这就是我们如何构建一个承诺对象来表示doLog

  var myPromise =新的Promise(doLog); 

我们所做的只是将doLog传递给promise构造函数,现在我们有了一个promise对象,表示要由doLog执行的任务。 当我们执行以上代码时会发生什么? 我们在控制台中看到以下内容:

您看到创建表示函数doLog时会看到什么? 创建一个promise对象仅称为函数。

考虑一下。 new Promise(doLog)具有与doLog()完全相同的效果。 promise构造函数只是运行传递给它的函数。

承诺对象

为了进一步了解promise对象的功能,让我们看看myPromise对象包含哪些信息。 如果在控制台中键入myPromisemyPromise看到以下内容:

myPromise具有两个属性- [[PromiseStatus]][[PromiseValue]] 。 值未定义时状态为待定。

为什么状态待定? 该承诺表示的操作doLog已完成。 状态不应该是“完成”之类的吗?

状态为待定的原因是Promise对象不知道任务已完成 。 就承诺而言, doLog可能已经发出了AJAX调用,启动了计时器或开始了动画,而动画将需要几秒钟才能完成。 Promise对象无法知道这一点-它所知道的就是已将其传递给函数来执行。

doLog的责任是告知promise对象何时完成,promise对象为我们提供了一种方法。 看一下对我们的代码的以下修改:

 函数doLog(resolve,reject){ 
console.log('Hello World!');
解决();
}
var myPromise =新的Promise(doLog);

当promise构造函数调用doLog ,它将传递给它两个函数,分别命名为resolverejectdoLog可以调用resolve来使doLog对象知道任务已完成,也可以调用reject来使对象知道我们的任务失败了。 在调用resolvereject之前,承诺将一直处于待处理状态。

当以上代码完成执行时, myPromise的状态将完成。

试验长时间运行的任务

这是事情开始变得有趣的地方。 承诺旨在表示异步过程,因此让我们尝试使用超时等实验:

 函数doWork(解决,拒绝){ 
setTimeout( function(){
console.log('工作完成');
解决();
} ,5000);
}
var myPromise =新的Promise(doWork);

注意我用粗体突出显示的回调函数。 在调用doWork之后5秒钟将调用它(超时为5秒),并且myPromise将保持等待状态5秒钟。 一旦过了5秒钟, myPromise将得到解决。 我鼓励您按F-12 ,将代码放入控制台,然后查看myPromise在5秒后如何更改状态。

。然后()

我们可以调用then()将函数附加到Promise对象,当Promise被解决或拒绝时将调用该对象。 看一下下面的代码:

 函数doWork(解决,拒绝){ 
setTimeout(function(){
console.log('工作完成');
解决();
},5000);
}
var myPromise =新的Promise(doWork);
myPromise.then(function(){
警报('成功!');
},函数(){
alert('错误!');
});

请注意,我们已经向then传递了两个函数。 承诺被解决后,第一个函数将被调用(如果承诺已被解决,则将立即被调用),而承诺被拒绝时,第二个函数被调用。 当我们执行此代码时,将在5秒钟后在控制台中看到“工作已完成”,紧接着是一条警告消息,显示“成功!”。 之所以会发生这种情况,是因为在兑现承诺后,我们传递给该函数的第一个函数将被正确调用。 如果诺言被拒绝,第二个函数将被调用。

承诺值

在本文的开头,我们看到了promise对象具有两个属性-value和status。 现在,我们确切地知道了promise状态是什么,但是我们没有看过value属性的作用。

简而言之,我们可以将一个值传递给resolvereject函数,并将传递给它们的任何值都设置为promise值。 此外,传递给我们用来resolvereject的值也传递给我们使用then附加到promise对象的函数。 让我们看一个例子:

 函数doWork(解决,拒绝){ 
setTimeout(function(){
resolve('工作完成!');
},5000);
}
var myPromise =新的Promise(doWork);
myPromise.then(function(msg){
alert('Success:'+ msg);
});

让我们将所有这些信息放在一起,并创建一个表示AJAX调用的承诺。

放在一起:AJAX调用

这就是使用Promise表示AJAX调用的方式。 为了简单起见,我使用的是jQuery的ajax函数。

 函数myAjaxCall(){ 
返回新的Promise(函数(解决,拒绝){
$.ajax({
url: '/ajax/imaginary_url.html',
success: function (data) {
resolve(data);
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}

注意,这次我们将函数直接传递给了promise构造器,而不是先声明了它。 然后在代码的后面:

  myAjaxCall()。then(function(data){ 
//当ajax调用成功时将调用此方法
},函数(错误){
//如果有错误将被调用
});

我现在想向我的读者传达一个啊哈哈的想法,可能是在看到上述情况之后发生的:

将AJAX调用从回调转换为Promise完全没有用。

这是真的。 我们可以使用回调方法轻松地完成相同的操作。 不用使用then来将方法附加到promise上,我们只需将两个方法传递到myAjaxCall ,它就可以正常工作。 在不涉及promise对象的情况下进行此AJAX调用可能会更容易

那么诺言的真正意义是什么?

承诺链接并避免回调地狱

承诺的真正力量来自于兑现承诺的能力。 由于函数本身会返回一个Promise,因此这是可能的。

如果您要从通过then附加的处理程序中返回一个诺言, then当您返回的诺言得到解决时,诺言返回将得到解决。 如果从使用then附加的处理程序中返回一个简单值,则then返回的promise将处于已解决状态,返回值将作为promise值。 我知道这听起来很复杂,所以让我们看一个例子:

  //此函数返回一个以毫秒为单位解析的promise 
函数wait(msec){
返回新的Promise(function(resolve){
setTimeout(resolve,msec);
});
}

函数调用wait(3000)将返回一个3秒钟内解决的承诺。 调用wait(6000)将返回一个在6秒内解决的承诺。 现在看一下以下内容:

  wait(3000) .then(function(){ 
console.log('等待3秒!');
返回wait(6000);
//注意我们正在兑现承诺
})。then(function(){
//前一个处理程序返回了一个承诺
//这意味着我们现在将等待该诺言解决
//解决后,此处理程序执行
console.log('等待6秒!');
返回5;
})。then(function(val){
//最后一个处理程序返回一个数字
//此处理程序将立即作为promise调用
//最后一次.then()调用返回的结果立即解析
console.log('这将立即被调用');
console.log('传递的值是:'+ val);
});

我知道以上内容可能有点难以理解。 如果不清楚,我鼓励您尝试使用它,因为在继续之前正确了解以上代码中发生的事情非常重要。

通过承诺实现更好代码的示例

想象一下,您必须发送一个AJAX调用,在ajax调用返回后一起启动两个动画,在两个动画结束时启动第三个动画,并在动画结束后将数据放到屏幕上。 那是四个异步操作。 这就是回调的样子:

  var oneAnimationComplete = false; 
  doAjax(函数(数据){ 
doAnimation1(function(){
onAnimationComplete(data);
});
doAnimation2(function(){
onAnimationComplete(data);
});
});
 函数onAnimationComplete(data){ 
如果(oneAnimationComplete){
doFinalAnimation(function(){
renderData(数据);
});
}
否则oneAnimationComplete = true;
}

您可以看到该代码不是很直观。 看到不同操作的顺序并不容易,因为我们传递的不同功能散布在各处。 注意,确保在animation1和animation2都完成之后调用最后一个动画的笨拙方式。 像这样的更多代码,您将深入回调领域,希望您选择编织而不是编程。

有了promise,并特别感谢promise链,您可以这样编写:

 让数据= {}; 
doAjax().then( function(d){
数据= d;
返回Promise.all(doAnimation1(),doAnimation2());
//Promise.all 返回已解决的新承诺
//当所有传递给它的承诺都解决时
})。then(function(){
返回finalAnimation();
})。then(function(){
renderData(数据);
});

您会看到很大的不同。 这些操作应一个接一个地执行,并保证代码可以清晰地捕获该流。 我们不再需要凌乱的嵌套回调,现在我们可以像同步代码一样接连放置代码行。 进行ajax调用,然后同时进行 animation1和animation2,然后进行最终动画,然后渲染数据。 该代码可读,可捕获实际的操作流程。

注意Promise.all的使用-它返回一个新的promise,当传递给它的所有promise都被解析时,该promise将被解析。 Promise.all是承诺带来的众多风声之一。

这是一篇出色的文章,它加强了允诺的概念,使我们可以模仿同步代码。 最后,不要忘了最终的资源:Mozilla开发者网络上关于诺言的部分。

随着Aura Component API返回对异步操作的承诺以及以JavaScript为重点的Lightning Web Components的出现,对于我们Salesforce开发人员而言,必须对承诺进行适当的理解以武装自己。 承诺提供了很多,但可能很难入门,我希望我的文章对您有所帮助,但请记住要继续探索。