前言

在实际项目中我们常会去用已经封装好的promise如axios,或者也会自己去封装promise,甚至在面试中,关于promise的面试题也层出不穷,promise的重要性不言而喻,故写该文章记录并加深自身对promise的理解。

背景

JavaScript是单线程语言,所以在执行耗时较大的任务时会导致页面阻塞。为了解决页面阻塞,异步执行被提了出来。但是异步带来一个执行顺序的问题,如果现在想要异步任务按照顺序依次执行,该怎么实现呢?

我们知道,通过回调函数的嵌套,可以实现异步任务的依次执行,但是嵌套的回调函数代码耦合性强,阅读起来不直观,一旦嵌套过多,就会产生所谓的回调地狱,会带来以下问题:

  1. 代码臃肿
  2. 可读性差
  3. 代码复用性差
  4. 耦合高,维护性差
  5. 只能在回调中处理异常
doSomething(function(result) {doSomethingElse(result, function(newResult) {doThirdThing(newResult, function(finalResult) {console.log('Got the final result: ' + finalResult);}, failureCallback);}, failureCallback);
}, failureCallback);

什么是promise?

那如何让我们的异步编程看起来更优雅,易读,易维护呢,ES6为我们提供了其标准内置对象Promise。我们可以通过.then().catch()等方法,采用链式编程的方式获取异步任务的执行结果或者执行下一个异步任务,下面的代码是否更加优雅易读了呢?

doSomething().then(function(result) {return doSomethingElse(result);
})
.then(function(newResult) {return doThirdThing(newResult);
})
.then(function(finalResult) {console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback)

当然,.then()里的回调函数也可以用箭头函数的形式来简写

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

如何使用?

1.创建一个Promise的实例。

我们可以通过new一个Promise的构造函数,传入的参数为一个回调函数,回调函数传入了两个参数,分别为resolve回调函数和reject回调函数,当异步任务执行成功时,可以将我们需要的执行结果作为resolve的参数传递出来,当异步任务请求失败时,将执行失败的结果作为reject的参数传递出来。

2.通过.then()或者.catch()等方法捕获promise的执行结果。

.then()可以传入两个回调函数作为参数,promise会将执行成功或者失败的结果的返回值作为参数,分别传给第一个和第二个回调函数,我们就可在回调函数中拿到他们。.catch()则是直接捕获promise失败的结果。那如何在promise中设置成功和失败结果的返回值呢?详见第二个示例代码。

下面是关于promise的两个案例,有助于理解promise的使用。

示例代码一 promise的简单使用

<script type="text/javascript">/*1. Promise基本使用我们使用new来构建一个Promise  Promise的构造函数接收一个参数,是函数,并且传入两个参数:          resolve,reject, 分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数*/var p = new Promise(function(resolve, reject){//2. 这里用于实现异步任务  setTimeoutsetTimeout(function(){var flag = false;if(flag) {//3. 正常情况resolve('hello');}else{//4. 异常情况reject('出错了');}}, 100);});//  5 Promise实例生成以后,可以用then方法指定resolved状态和reject状态的回调函数 //  在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了  p.then(function(data){console.log(data)   // 成功的值},function(info){console.log(info)  // 失败的值});</script>

示例代码二 基于promise的ajax请求

<script type="text/javascript">/*基于Promise发送Ajax请求*/function queryData(url) {#   1.1 创建一个Promise实例var p = new Promise(function(resolve, reject){var xhr = new XMLHttpRequest();xhr.onreadystatechange = function(){if(xhr.readyState != 4) return;if(xhr.readyState == 4 && xhr.status == 200) {# 1.2 处理正常的情况resolve(xhr.responseText);}else{# 1.3 处理异常情况reject('服务器错误');}};xhr.open('get', url);xhr.send(null);});return p;}# 注意:  这里需要开启一个服务 # 在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了queryData('http://localhost:3000/data').then(function(data){console.log(data)#  1.4 想要继续链式编程下去 需要 return  return queryData('http://localhost:3000/data1');}).then(function(data){console.log(data);return queryData('http://localhost:3000/data2');}).then(function(data){console.log(data)});</script>

Promise 基本API

实例方法

.then()

  • 得到promise内部任务的执行结果。

.catch()

  • 得到promise内部任务执行失败的结果。

.finally()

  • 成功与否都会执行 在项目中 我们基于axios发送请求时(axios其实也是一个基于ajax用promise封装的http请求库),往往会开启loading,我们可以通过finally,不管请求成功与否,关闭loading,而不需要在成功和失败的时候写两次关闭loading代码。)

  •  <script type="text/javascript">/*Promise常用API-实例方法*/// console.dir(Promise);function foo() {return new Promise(function(resolve, reject){setTimeout(function(){// resolve(123);reject('error');}, 100);})}// foo()//   .then(function(data){//     console.log(data)//   })//   .catch(function(data){//     console.log(data)//   })//   .finally(function(){//     console.log('finished')//   });// --------------------------// 两种写法是等效的foo().then(function(data){# 得到异步任务正确的结果console.log(data)},function(data){# 获取异常信息console.log(data)})# 成功与否都会执行(不是正式标准) .finally(function(){console.log('finished')});</script>

.all()

  • Promise.all方法接受一个数组作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例决定

小案例 

现在有这样的一个需求,执行按钮的触发条件是必须以上两个表单都验证成功,那么可以通过将每个表单的验证结果包装成promise对象,通过Promise.all 实现

// 这是一段伪代码function promise1() {return new Promise(function (resolve, reject) {this.$refs.form1.validate(v => {if(v) resolve()})})}function promise2() {return new Promise(function (resolve, reject) {this.$refs.form2.validate(v => {if(v) resolve()})})}Promise.all([promise1(), promise2()]).then(resolve => console.log('按钮触发事件')).catch((e) => console.log(e))

.race()

  • Promise.race方法同样接受一个数组作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilledrejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。

<script type="text/javascript">/*Promise常用API-对象方法*/// console.dir(Promise)function queryData(url) {return new Promise(function(resolve, reject){var xhr = new XMLHttpRequest();xhr.onreadystatechange = function(){if(xhr.readyState != 4) return;if(xhr.readyState == 4 && xhr.status == 200) {// 处理正常的情况resolve(xhr.responseText);}else{// 处理异常情况reject('服务器错误');}};xhr.open('get', url);xhr.send(null);});}var p1 = queryData('http://localhost:3000/a1');var p2 = queryData('http://localhost:3000/a2');var p3 = queryData('http://localhost:3000/a3');Promise.all([p1,p2,p3]).then(function(result){//   all 中的参数  [p1,p2,p3]   和 返回的结果一 一对应["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]console.log(result) //["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]})Promise.race([p1,p2,p3]).then(function(result){// 由于p1执行较快,Promise的then()将获得结果'P1'。p2,p3仍在继续执行,但执行结果将被丢弃。console.log(result) // "HELLO TOM"})</script>

以上基本涵盖了promise的常见用法。

promise 的状态改变

Promise在执行时,其状态也会发生相应的改变。

peding: Promise执行时,其内部任务还未执行,.then还未拿到执行结果。(初始化时,执行器函数执行(同步执行),如果同步的直接改变状态,则可以看成一个段同步代码)

resolved:内部任务执行完毕,状态为成功。(resolved被调用时)

rejected:内部任务执行完毕,状态为失败。(rejected被调用时)

1. pending 变为 resolved

2. pending 变为 rejected

说明: 只有这 2 种, 且一个 promise 对象只能改变一次 无论变为成功还是失败, 都会有一个结果数据 成功的结果数据一般称为 vlaue, 失败的结果数据一般称为reason。

是不是有些懵逼?咱们不废话,直接上代码。

// 由于Promise传入的回调函数是同步任务 故会在执行栈立即执行const p = new Promise((resolve, reject) => { // 发现宏任务,放入浏览器宿主环境等待执行setTimeout(() => {console.log('异步执行完了');resolve('成功')}, 2000)})// .then()为微任务 等待当前所有同级的同步任务和宏任务执行完再执行p.then((res) => console.log('res', res)) // 同步任务立即执行console.log('p', p);

上面的代码 我们通过console面板输出以下结果:

是不是发现了什么不得了的东西?没错,代码执行输出p时,Promise的异步任务还未执行完,.then也还没拿到执行结果,故处于peding状态。那么同理,在resolved状态和rejected状态代码如下:

  const p = new Promise((resolve, reject) => {// 同步任务,立即执行console.log('异步执行完了');resolve('成功')})p.then((res) => console.log('res', res)) // 执行此行代码时 promise内的任务已经执行完毕 且状态为成功console.log('p', p);

  const p = new Promise((resolve, reject) => {// 同步任务,立即执行console.log('异步执行完了');reject('失败')})p.catch((res) => console.log('res', res)) // 执行此行代码时 promise内的任务已经执行完毕 且状态为成功console.log('p', p);

promise的基本流程

关于promise的执行顺序

当我们new一个Promise时,传入的回调函数为同步代码 ,会立即执行,而.then() .catch()里的为异步微任务。

console.log(1)
setTimeout(function(){console.log(2)
}, 0)
new Promise(function(resolve){ //这里的回调是同步的console.log(3)resolve()
}).then(function(){  //异步微任务console.log(4)
})
console.log(5)//输出结果 1,3,5,4,2

Promise的三个缺点

  • 1.无法取消Promise,一旦新建它就会立即执行,无法中途取消

  • 2.如果不设置回调函数,Promise内部抛出的错误,不会反映到外部

  • 3.当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成

注意:如果promise不通过.then或.catch方法捕获错误,在promise执行为reject状态时,会抛出一个reject错误,导致console面板报红,后面代码不在执行 ,我们也可通过配合async await 及try catch方法捕获错误执行相关逻辑操作。

 try {const res = await getArticleById(this.articleId) // getArticleById()是封装了的axios的api请求方法 本质也为一个promisethis.articleInfo = res.datathis.isLoading = falsethis.previewImage()} catch (err) {this.isLoading = falseif (err.response && err.response.status === 404) {this.is404 = true}}

JS 中关于Promise的用法,状态,执行顺序详解,面试可用(原创)相关推荐

  1. python两个装饰器执行顺序_python中多个装饰器的执行顺序详解

    装饰器是程序开发中经常会用到的一个功能,也是python语言开发的基础知识,如果能够在程序中合理的使用装饰器,不仅可以提高开发效率,而且可以让写的代码看上去显的高大上^_^ 使用场景 可以用到装饰器的 ...

  2. python装饰器的顺序_python中多个装饰器的执行顺序详解

    装饰器是程序开发中经常会用到的一个功能,也是python语言开发的基础知识,如果能够在程序中合理的使用装饰器,不仅可以提高开发效率,而且可以让写的代码看上去显的高大上^_^ 使用场景 可以用到装饰器的 ...

  3. vue进入页面执行的钩子函数_vue中各选项及钩子函数执行顺序详解

    {"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],&q ...

  4. Node.js中的不安全跳转如何防御详解

    为什么80%的码农都做不了架构师?>>>    Node.js中的不安全跳转如何防御详解 导语: 早年在浏览器大战期间,有远见的Chrome认为要运行现代Web应用,浏览器必须有一个 ...

  5. new Date将字符串转化成日期格式 兼容IE,ie8如何通过new Date将字符串转化成日期格式,js中如何进行字符串替换, replace() 方法详解

    new Date将字符串转化成日期格式 兼容IE,ie8如何通过new Date将字符串转化成日期格式,js中如何进行字符串替换, replace() 方法详解 //获得年月日时分秒 //传入日期// ...

  6. unity 继承会调用start吗_Unity 继承MonoBehaviour脚本 执行顺序 详解

    先看结果 Awake ->OnEnable-> Start ->-> FixedUpdate-> Update  -> LateUpdate ->OnGUI ...

  7. 【异步系列二】Promise原理及执行顺序详解

    前言 Promise 是 javascript 中非常重要的一环,熟悉它是必须的,而且在面试中也常常会问到相关面试题. 在了解 Promise 之前,需要了解什么是异步编程,可以参考我的一篇文章:Ja ...

  8. 前端开发:JS中let、var和const的区别详解

    前言 前端开发过程中,JS声明变量的关键字想必开发者都不陌生,而且使用的频率在前端开发过程中也是数一数二的.JS中声明变量的关键字有三个let.var和const,但是三者的使用对比和区别也是非常重要 ...

  9. Java 中 finally 与 return 的执行顺序详解

    java方法是在栈幀中执行,栈幀是线程私有栈的单位,执行方法的线程会为每一个方法分配一小块栈空间来作为该方法执行时的内存空间,栈幀分为三个区域: 1 . 操作数栈,用来保存正在执行的表达式中的操作数, ...

最新文章

  1. 世界公认最好的记忆方法_世界记忆大师:6种简单实用记忆方法,让孩子成为学霸中学霸...
  2. python条件替换_python-根据其他列中的条件替换pandas列中的某些特定值
  3. 8、泛型程序设计与c++标准模板库2.4列表容器
  4. 利用chrome的Timeline观测系统的内存回收逻辑
  5. asp文件上传和下载
  6. ffmpeg源码分析_ffmpeg音视频同步的几种策略
  7. b站whats app
  8. mysql中find_in_set()函数的使用(转载)
  9. 一个完整的SPC案例—从特性分析到CPK计算
  10. 易语言解析html实例,易语言总使用正则表达式实例解析
  11. 搜索巨头百度深耕中国量子产业
  12. 多种方式查看电脑是否支持Modern Standby
  13. 机动车合格证手机扫码开票实现方式
  14. ubuntu安装搜狗输入法老是没中文怎么办
  15. ArcGIS 保存后在另一台电脑上打开显示无数据的问题
  16. 如何判断视频的比例(4:3/16:9)和分辨率?
  17. backface-visibility
  18. ​CES已是技术创新的风向标  2019年哪项技术独领风骚?
  19. Error java 错误 不支持发行版本5 ( 完美解决版)
  20. 画图解释FHSS、DSSS扩频原理以及计算规则

热门文章

  1. uvc摄像头代码解析之描述符
  2. jQuery入门选择器
  3. 【网络安全】CSRF漏洞详细解读
  4. 软件设计模式——监听模式
  5. 如果房贷被拒了怎么办?
  6. 今天早上跑通了 PF-AFN!
  7. 禾瑞亚科触摸屏驱动程序移植过程与遇到的问题--egalax_i2c
  8. [转] linux操作系统下c语言编程入门
  9. 小船过河(贪心算法)
  10. jzoj5935小凯学数学