首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JavaScript基础——Promise使用指南

JavaScript基础——Promise使用指南

原创
作者头像
前端达人
修改于 2018-12-09 04:49:25
修改于 2018-12-09 04:49:25
1.1K00
代码可运行
举报
文章被收录于专栏:前端达人前端达人
运行总次数:0
代码可运行

在上篇文章里《JavaScript基础——回调(callback)是什么》我们一起学习了回调,明白了回调就是一个在另外一个函数执行完后要执行的函数,如果我们希望异步函数能够像同步函数那样顺序执行,只能嵌套使用回调函数,过多的回调嵌套会使得代码变得难以理解与维护,为了避免“回调地狱”让人发狂的行为,ES6原生引入了promise的模式,通过这种方式,让我们代码看起来像同步代码,大大简化了异步编程,简直是ES6新特性中最让我们兴奋的特性之一。

什么是promise?

首先我们看看promise这个单词的中文释义,作为名词解释为承诺、诺言、誓言、约言,从中文释义可以看出,是一个未发生,将来一定会发生的某种东东…… 接下来我们来看看ECMA委员会怎么定义Promise的:

A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.

Promise是一个对象,用作占位符,用于延迟(可能是异步)计算的最终结果。

简单的来说,Promise就是装载未来值的容器。其实生活中有很多Promise的场景,设想以下的场景:我们去快餐店点餐,你点了一份牛肉面,你扫码付款后,会拿到一份带订单号的收据。订单号就是快餐店给我们的一份牛肉面的承诺(promise),保证了你会得到一份牛肉面。

所以我们一定要包管好我们的订单收据,因为我们知道了这个收据代表了我们未来会有一份牛肉面,尽管快餐店不能马上给我们一份牛肉面,但是我们大脑潜意识的把订单收据当做牛肉面的“占位符”了

终于,我们听到服务员在喊“100号的牛肉面好了,请到窗口取餐”,然后我们拿着订单收据来到窗口递给服务员,我们换来了牛肉面。

说了很多,简单描述这个概念就是一旦我们需要的值准备好了,我们就用对我的承诺值换取这个值本身。

但是,还有一种不好的结果,服务员叫到我们的订单号,当我们去拿的时候,服务员会一脸歉意的告诉我们“十分抱歉,您的牛肉面卖完了”。作为顾客的我们对这个情况,除了愤怒之外只能换个地吃饭了或者点其它的。从中我们可以看出,未来值还有一个重要的特性:它可能成功也可能失败。

生活的例子很简单我们都经历过,我们是不是特别着急如何用Promise呢?在使用之前,我们还是先了解下——Promise State(承诺状态,注:暂且这么翻译,小编也不知道如何翻译更好)  

Promise State(承诺状态)

Promise只会处在以下状态之一:

Pending(待处理): promise初始化的状态,正在运行,既未完成也没有失败的状态,此状态可以迁移至fulfilled和rejected状态。

Fulfilled(已完成):如果回调函数实现Promise的resolve回调(稍后介绍),那我们的promise实现兑现。

Rejected(已拒绝):如果Promise调用过程中遭到拒绝或者发生异常,那么我们的promise被拒绝,处于Rejected(状态)。

Settled(不可更改的):Promise如果不处在Pending状态,状态就会改变,要不是Fulfilled要不是Rejected这两种状态。

Promise的状态转换,可以用下面一张图进行表示(图片来源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#Methods

Promise vs callback

比如我们有个需求,需要通过AJAX实现三个请求,第二个和第三个请求都依赖上一个接口的请求,如果使用CallBack的方式,我们的代码可能是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ajaxCall('http://example.com/page1', response1 => { 
 ajaxCall('http://example.com/page2'+response1, response2 => { 
  ajaxCall('http://example.com/page3'+response2, response3 => { 
  console.log(response3) 
  } 
 }) 
}) 

大家很快就会发现,这种多重嵌套的代码不但难以理解,而且难以维护,这就是著名的“回调地狱”现象。

如果使用Promise则会让我们的大脑更容易接受和理解,代码显得简单扁平化,代码调用如下,如何实现ajaxCallPromise稍后介绍:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ajaxCallPromise('http://example.com/page1') 
.then( response1 => ajaxCallPromise('http://example.com/page2'+response1) ) 
.then( response2 => ajaxCallPromise('http://example.com/page3'+response2) ) 
.then( response3 => console.log(response3) ) 

你是不是觉得代码的复杂性突然降低,代码看起来更简单易读呢,你也许会问ajaxCallPromise怎么写?,别着急,接着往下看

Promise实现——(resolve, reject) 方法

要实现回调函数转换成Promise对象,我们需要使用Promise构造函数,在上一小节,小编展示了ajaxCallPromise函数是如何调用的,ajaxCallPromise的实现内容如下,小编实现了(resolve,reject)相应的回调函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const ajaxCallPromise = url => { 
 return new Promise((resolve, reject) => { 
 // DO YOUR ASYNC STUFF HERE 
 $.ajaxAsyncWithNativeAPI(url, function(data) { 
  if(data.resCode === 200) { 
  resolve(data.message) 
  } else { 
  reject(data.error) 
  } 
 }) 
 }) 
} 

如何理解这段代码呢?

  1. 首先定义ajaxCallPromise返回类型为Promise,这意味我们会实现一个Promise的承诺。
  2. Promise接受两个函数参数,resolve(成功实现承诺)和reject(异常或失败)
  3. resolve和reject这两个特有的方法,会获取对应成功或失败的值
  4. 如果接口请求一切正常,我们将会通过resolve函数接收返回的值
  5. 如果接口请求失败,我们将会通过reject回调接收失败返回的值

再举个简单的例子,如果foo()和bar()函数都实现promise,我们改怎么写呢?

方式一:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
foo().then( res => { 
 bar().then( res2 => { 
  console.log('Both done') 
 }) 
}) 

方式二(建议这种,简单易读)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
foo() 
.then( res => bar() ) // bar() returns a Promise 
.then( res => { 
 console.log('Both done') 
}) 

.then(onFulfilled, onRejected) 方法

Promise的then()方法允许我们在任务完成后或拒绝失败后执行相应的任务,该任务可以是基于另外一个事件或基于回调的异步操作。

Promise的then()方法接收两个参数,即onFulfilled 和 onRejected 的回调,如果Promise对象完成,如果成功状态则执行onFulfilled回调,如果异常或失败则执行onRejected回调。

简单的来说,onFulfilled回调接收一个参数,及所谓的未来的值,同样 onRejected 也接收一个参数,显示拒绝的原因。让我们改动下上小节ajaxCallPromise的then()方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ajaxCallPromise('http://example.com/page1').then( 
 successData => { console.log('Request was successful') }, 
 failData => { console.log('Request failed' + failData) } 
) 

如果请求过程失败,第二个函数将会执行输出而不是第一个函数输出。

我们一起再来看个简单的例子,我们在setTimeout()实现Promise回调,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const PsetTimeout = duration => { 
 return new Promise((resolve, reject) => { 
  setTimeout( () => { 
   resolve() 
  }, duration); 
 }) 
} 
PsetTimeout(1000) 
.then(() => { 
 console.log('Executes after a second') 
}) 

这里我们在这里实现了一个成功状态后没有返回成功状态值的Promise,函数执行后,成功返回后未来值将会是 undefined.

catch(onRejected)方法

除了then()方法可以处理错误和异常,使用Promise的catch()方法也能实现同样的功能,这个方法其实并没有什么特别,只是更容易理解而已,我们一眼就能明白是捕获异常的操作。

catch()方法只接收一个回调函数。catch()方法的onRejected回调的调用方式与then()方法的onRejected回调相同。

还记得我们上小节ajaxCallPromise的then()方法的实现吗:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ajaxCallPromise('http://example.com/page1').then( 
 successData => { console.log('Request was successful') }, 
 failData => { console.log('Request failed' + failData) } 
) 

我们还可以使用catch()方法进行捕获异常或拒绝,效果是一致的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ajaxCallPromise('http://example.com/page1’) 
.then(successData => console.log('Request was successful’)) 
.catch(failData => console.log('Request failed' + failData)); 

Promise.resolve(value)方法

Promise的resolve()方法接收成功返回值并返回一个Promise对象,用于未来值的传递,将值传递给.then(onFulfilled, onRejected) 的onFulfilled回调中。resolve()方法可以用于将未来值转化成Promise对象,下面的一段代码演示了如何使用Promise.resolve()方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const p1 = Promise.resolve(4); 
p1.then(function(value){ 
 console.log(value); 
}); //passed a promise object 
Promise.resolve(p1).then(function(value){ 
 console.log(value); 
}); 
Promise.resolve({name: "Eden"}) 
.then(function(value){ 
 console.log(value.name); 
}); 

控制台将会输出以下内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
4 
4 
Eden 

Promise.reject(value)方法

Promise.reject(value)方法与上小节Promise.resolve(value)类似,唯一不同的是将值传递给.then(onFulfilled, onRejected) 的onRejected回调中,同时Promise.reject(value)主要用来进行调试。

让我们看看下面一段代码如何使用Promise.reject(value)方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const p1 = Promise.reject(4); 
p1.then(null, function(value){ 
console.log(value); 
}); 
Promise.reject({name: "Eden"}) 
.then(null, function(value){ 
 console.log(value.name); 
}); 

控制台将输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
4 
Eden 

Promise.all(iterable) 方法

该方法传入迭代的Promise数组对象,并一起返回一个Promise对象,当所有的Promise迭代对象成功返回后,整个Promise才能返回成功状态的值。

好了,我们一起看看怎么实现Promise.all(iterable) 方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const p1 = new Promise(function(resolve, reject){ 
 setTimeout(function(){ 
 resolve(); 
 }, 1000); 
}); 
const p2 = new Promise(function(resolve, reject){ 
setTimeout(function(){ 
 resolve(); 
 }, 2000); 
}); 
const arr = [p1, p2]; 
Promise.all(arr).then(function(){ 
console.log("Done"); //"Done" is logged after 2 seconds 
}); 

特别需要注意的一点,在迭代数组中,只要任意一个进入失败状态,那么该方法返回的对象也会进入失败状态,并将那个进入失败状态的错误信息作为自己的错误信息,示例代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const p1 = new Promise(function(resolve, reject){ 
 setTimeout(function(){ 
 reject("Error"); 
 }, 1000); 
}); 
const p2 = new Promise(function(resolve, reject){ 
 setTimeout(function(){ 
 resolve(); 
 }, 2000); 
}); 
const arr = [p1, p2]; 
Promise.all(arr).then(null, function(reason){ 
console.log(reason); //"Error" is logged after 1 second 
}); 

Promise.race(iterable) 方法

与Promise.all(iterable) 不同的是,Promise.race(iterable) 虽然也接收包含若干个Promise对象的可迭代对象,不同的是这个方法会监听所有的Promise对象,并等待其中的第一个进入完成或失败状态的Promise对象,一旦有Promise对象满足,整个Promise对象将返回这个Promise对象的成功状态或失败状态,下面的示例展示了返回第一个成功状态的值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var p1 = new Promise(function(resolve, reject){ 
setTimeout(function(){ 
resolve("Fulfillment Value 1"); 
}, 1000); 
}); 
var p2 = new Promise(function(resolve, reject){ 
setTimeout(function(){ 
resolve("fulfillment Value 2"); 
}, 2000); 
}); 
var arr = [p1, p2]; 
Promise.race(arr).then(function(value){ 
console.log(value); //Output "Fulfillment value 1" 
}, function(reason){ 
console.log(reason); 

用Promise改写上篇文章的回调方法

读过《JavaScript基础——回调(callback)是什么》文章同学,文章的最后我们用回调函数实现了一个真实的业务场景——用NodeJs实现从论坛帖子列表中显示其中的一个帖子的信息及留言列表信息,如果使用本篇文章学习到的内容,我们如何实现呢, 代码如下:

index.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const fs = require('fs'); 
const path = require('path'); 
const postsUrl = path.join(__dirname, 'db/posts.json'); 
const commentsUrl = path.join(__dirname, 'db/comments.json'); 
//return the data from our file 
function loadCollection(url) { 
 return new Promise(function(resolve, reject) { 
  fs.readFile(url, 'utf8', function(error, data) { 
   if (error) { 
    reject('error'); 
   } else { 
    resolve(JSON.parse(data)); 
   } 
  }); 
 }); 
} 
//return an object by id 
function getRecord(collection, id) { 
 return new Promise(function(resolve, reject) { 
  const data = collection.find(function(element){ 
   return element.id == id; 
  }); 
  resolve(data); 
 }); 
} 
//return an array of comments for a post 
function getCommentsByPost(comments, postId) { 
 return comments.filter(function(comment){ 
  return comment.postId == postId; 
 }); 
} 
//initialization code 
loadCollection(postsUrl) 
.then(function(posts){ 
 return getRecord(posts, "001"); 
}) 
.then(function(post){ 
 console.log(post); 
 return loadCollection(commentsUrl); 
}) 
.then(function(comments){ 
 const postComments = getCommentsByPost(comments, "001"); 
 console.log(postComments); 
}) 
.catch(function(error){ 
 console.log(error); 
}); 

结束语:

本篇的内容就介绍到这里,各位是否看的很过瘾,虽然Promise已经比回调函数好许多,但是还是不够简洁,不够符合我们人类大脑思考逻辑,如果我们能以书写同步的方法书写异步代码,那该多好啊,ES8引入了async/await让我们能用同步的方式书写异步代码,想想就很激动,小编将会在下篇文章进行介绍,敬请期待!

更多精彩内容,请微信关注”前端达人”公众号!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
分布式配置中心选型,为什么选择Apollo?
关于配置的常规方案是将配置信息抽离写入 xml、properties文件中,然后随着应用一块打包发布。如果有开发、测试、预发、生产等多套环境,则通过配置各自独立的文件以区分不同的环境。具备一定的扩展性,但每次配置参数变更都要重新发布应用,灵活性较差。
微观技术
2021/04/30
1.4K1
分布式配置中心选型,为什么选择Apollo?
springboot使用nacos做配置中心
分布式配置中心有多个,包括Apollo、Disconf等等,已经有大神做了各种对比,技术没有好坏,选择适合自己的最好,我们选择nacos是结合了它的服务发现和服务管理,这一块后面再说,今天我们先来看看如何使用它的配置管理。
小尘哥
2021/03/03
6340
springboot使用nacos做配置中心
SpringBoot项目使用配置中心Nacos
从实体类可以知道表格很简单就只有id、username、password三个字段。
BUG弄潮儿
2020/07/10
3.5K0
SpringBoot项目使用配置中心Nacos
Java规则引擎 Easy Rules
出处:https://www.cnblogs.com/cjsblog/p/13088017.html
JAVA葵花宝典
2020/06/17
4.3K0
Java规则引擎 Easy Rules
几行代码就可以使用分布式配置中心,Spring Cloud Alibaba真香
当我们在业务服务中需要动态更新配置信息时,就需要引入分布式配置中心,那么市面上开源的分布式配置中心有很多,那么我们该选择哪些呢?比如Nacos、Apollo以及Disconf等等。
35岁程序员那些事
2022/09/23
2030
几行代码就可以使用分布式配置中心,Spring Cloud Alibaba真香
重学SpringCloud系列一之微服务入门
小夫妻俩刚结婚,手里资金有限,就想着开一个路边烧烤摊。丈夫负责烤串做菜、妻子负责服务收银及上菜。这是一个典型的路边烧烤摊的经营模式。大家仔细看这种经营模式的好处在于:
大忽悠爱学习
2022/05/07
4000
重学SpringCloud系列一之微服务入门
spring的整合分布式配置中心(ACM diamond nacos Apollo)-nacos
继上文:spring的整合分布式配置中心(ACM diamond nacos Apollo)
逍遥壮士
2020/12/08
1.3K0
【零开始搭建SpringCloud Alibaba】搭建nacos应用端
前面介绍了搭建Nacos应用端(服务发现),这里是使用到Nacos作为分布式配置中心的功能所需要的nacos应用端。
RRT冻羊
2022/11/03
3660
【零开始搭建SpringCloud Alibaba】搭建nacos应用端
Nacos入门指南04 - 分布式配置实践
(3)SpringBoot 服务整合 Nacos,改为使用 Nacos 中的配置。
dys
2020/11/01
7990
Nacos入门指南04 - 分布式配置实践
SpringBoot使用Nacos配置中心
Nacos是阿里巴巴集团开源的一个易于使用的平台,专为动态服务发现,配置和服务管理而设计。它可以帮助您轻松构建云本机应用程序和微服务平台。
lyb-geek
2019/03/07
3.9K0
SpringBoot使用Nacos配置中心
Nacos Config--服务配置
Apollo是由携程开源的分布式配置中心。特点有很多,比如:配置更新之后可以实时生效,支持灰度发 布功能,并且能对所有的配置进行版本管理、操作审计等功能,提供开放平台API。并且资料也写的很 详细。
IT小马哥
2021/09/03
5260
Nacos Config--服务配置
sentinel的实际应用
上一章中我们通过Dashboard来为Sentinel客户端设置各种各样的规则,但是这些规则默认是存放在内存中,极不稳定,无法用于生成环境,所以需要将其持久化。
Java旅途
2020/10/22
1.3K0
【Nacos系列教程】Nacos第二篇:配置中心演示一
在上一篇,我们讲解了,怎么在Windows系统中启动一个nacos。我们就使用这个nacos来作为配置中心
凯哥Java
2022/12/16
7540
【Nacos系列教程】Nacos第二篇:配置中心演示一
Nacos配置中心落地与实践
目前,我们公司各团队配置中心使用各异,电商使用的是 Spring Cloud Config,支付使用的是 Apollo,APP 团队使用的是 Apollo+Nacos。为了更好地应对公司业务的发展,统一基础设施技术栈必不可少。
杨同学technotes
2022/12/01
9500
重学SpringCloud系列四之分布式配置中心---上
为了避免参数变化引起的频繁的程序改动,通常我们在应用程序中将常用的一些系统参数、启动参数、数据库参数等等写到配置文件或其他的存储介质里面。
大忽悠爱学习
2022/05/09
8870
重学SpringCloud系列四之分布式配置中心---上
【万字长文】创业公司就应该技术选型 Spring Cloud Alibaba , 开箱即用
互联网时代,面对复杂业务,讲究 分而治之。将一个大的单体系统拆分为若干个微服务,保证每个系统的职责单一,可以垂直深度扩展。
微观技术
2022/02/10
6650
【万字长文】创业公司就应该技术选型 Spring Cloud Alibaba , 开箱即用
我们放弃了Nacos作为配置中心,转而选择了这款神器~
目前,我们所有微服务的配置中心都没有采用Nacos,而是选择了另一款携程开源的分布式配置中心Apollo,今天就跟大家详细介绍一下这款神级配置中心
用户1220090
2025/04/29
3580
我们放弃了Nacos作为配置中心,转而选择了这款神器~
Java学习笔记-微服务(2)-原生服务注册Consul
Consul 是一个开源的分布式服务发现和配置管理系统,使用 Go 语言进行开发,它提供了微服务中的服务治理、配置中心、控制总线等功能,这些功能可以单独使用,也可以共同使用以构建全方位的服务网络。
咸鱼程序员
2025/03/03
1640
Java学习笔记-微服务(2)-原生服务注册Consul
快速学习-sentinel动态规则扩展
Sentinel 的理念是开发者只需要关注资源的定义,当资源定义成功后可以动态增加各种流控降级规则。Sentinel 提供两种方式修改规则:
cwl_java
2020/08/02
1.4K0
基于Nacos的服务治理、配置中心
参看《基于Docker搭建Nacos集群》:https://lupf.cn/articles/2020/05/21/1590058654840.html ;亦或者通过官方提供的其他方式安装,详情参考:https://nacos.io/zh-cn/docs/quick-start.html
一行Java
2022/04/06
8490
基于Nacos的服务治理、配置中心
推荐阅读
相关推荐
分布式配置中心选型,为什么选择Apollo?
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 什么是promise?
  • Promise State(承诺状态)
  • Promise vs callback
  • Promise实现——(resolve, reject) 方法
  • .then(onFulfilled, onRejected) 方法
  • catch(onRejected)方法
  • Promise.resolve(value)方法
  • Promise.reject(value)方法
  • Promise.all(iterable) 方法
  • Promise.race(iterable) 方法
    • 用Promise改写上篇文章的回调方法
    • 结束语:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档