工作中常常用到promise,async + await,遇到一些问题需要用到基础知识总会有一部分不记得,就重新温习权威指南和es6标准入门,花了几天肝下了这篇文章。喜欢的同学请动动发财手点个赞,文章有错误的地方还请指出。

例一

new Promise((resolve,reject) =>{
console.log('11')
resolve()
console.log('Promise')
})
console.log('start')

结果:

11
Promise
start

分析:

  1. Promise 新建后就会立即执行,从上往下执行,打印11
  2. 然后碰到resolve(),Promise状态由pending变为fulfilled
  3. 但调用resolvereject并不会终结 Promise 的参数函数的执行,所以会往下执行,打印出Promise
  4. 继续往下,打印出start

    总结:调用resolve或reject并不会终结 Promise 的参数函数的执行

再看一个代码类似的例子但执行过程却不一样

例二

new Promise((resolve, reject) => {
return resolve(1);
console.log(2);
}).then((res) =>{
console.log('then',res)
})

结果:

then 1

分析:

  1. Promise 新建后就会立即执行,碰到resolve(1),Promise状态由pending变为fulfilled
  2. 但这里加上了return,console.log(2)不会执行
  3. 执行.then(),打印出 then 1

    结果:Promise参数函数中加上return,后面的语句不会执行了,2不会被打印。.then().catch()中也是一样。

猜猜下面的执行会是什么

new Promise((resolve, reject) => {
resolve(1);
}).then((res) =>{
return 2;
console.log('then1',res)
}).then((res) =>{
console.log('then2',res)
throw new Error('error')
}).catch((err) =>{
return 'catch返回值'
console.log('catch',err)
}).then((res) =>{
console.log('then3',res)
})

结果:

then2 2
then3 catch返回值

分析:

  1. new Promise立即执行,resolve(1)1作为返回值传给.then()
  2. 执行.then(),遇到 return 2,相当于return Promise.resolve(2),由于加了return,后面的 then1不会被打印
  3. 执行第二个.then(),打印 then2 2,然后抛出一个错误,相当于执行了 return Promise.reject('error')
  4. 往下执行,.catch()捕获到了错误,并 return 'catch返回值',相当于return Promise.resolve('catch返回值'),同样下面的 cosnole.log('catch',err)不会执行
  5. 最后的.then()会接收到上面的返回值,并打印 then3 catch返回值

例三

Promise.resolve('start')
.then((res) =>{
console.log('then1',res)
return 11 //相当于 return Promise.resolve(11)
}).then((res) =>{
console.log('then2',res) //相当于 return Promise.resolve(undefined)
}).then((res) =>{
console.log('then3',res)
throw new Error('then3 error') //相当于 return Promise.reject(new Error('then3 error'))
}).catch((res) =>{
console.log('catch',res)
})

结果:

then1 start
then2 11
then3 undefined
catch Error: then3 error

分析:在Promise中,返回任意一个非 promise 的值都会被包裹成promise对象

例四

const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);

结果:

1 2 4

分析:

  1. new Promise立即执行,打印 12,
  2. 遇到promise.then(),由于Promise中没有resolve()或者reject(),所以promise的状态会一直是pending,promise.then()方法不会执行,3 不会被打印,
  3. 继续往下,打印4

    总结:promise里面没有resolve()或reject(),promise会一直是pending状态,并且不会执行对应的.then()或者.catch()

例五:.then()的参数

new Promise((resolve,reject) =>{
resolve('start')
})
.then(1)
.then(Promise.resolve(2))
.then((res) =>{
console.log(res)
})

结果:

start

分析:

  1. Promise的.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传,所以.then(1)相当于.then((res) => Promise.resolve(res)),
  2. .then(Promise.resolve(2))相当于
.then((res) => {
Promise.resolve(2)
return Promise.resolve(res)
})
  1. 最后的.then()打印start

总结:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传

将上面例子中再加入一行.then(console.log),打印结果又有所不同了

new Promise((resolve,reject) =>{
resolve('start')
})
.then(1)
.then(Promise.resolve(2))
.then(console.log)
.then((res) =>{
console.log('res',res)
})

结果:

start
res undefined

分析:

  1. start是由第三个.then()打印的,
  2. .then(console.log)相当于
.then((res) =>{
console.log(res)
return Promise.resolve(undefined)
})

例六:Promise的状态

const p = new Promise((resolve,reject) =>{
resolve('11')
})
p.then(() =>{
console.log('then1',p)
})
p.then(() =>{
console.log('then2',p)
})

结果:

then1 Promise {<fulfilled>: '11'}
then2 Promise {<fulfilled>: '11'}

分析:Promise的状态一经改变就不能再改变

例七:.catch()处理错误

new Promise((resolve,reject) =>{
resolve()
}).then((res) =>{
throw new Error('throw error')
},(err) =>{
console.log('err',err)
}).catch((err) =>{
console.log('catch:',err)
})

结果:

catch: Error: throw error

分析:对promise而言,处理错误可以给.then()方法传递第二个函数,但问题在于.then()中的第二个函数无法捕获第一个函数出现的错误。而用.catch()则能捕获到上层抛出的错误

例八:.catch()回调

new Promise((resolve,reject) =>{
reject()
}).catch((err) =>{
return 'catch1 正常返回了'
}).then((res) =>{
console.log('then1:',res)
}).catch((err) =>{
console.log('catch2:',err)
})

结果:

then1: catch1 正常返回了

分析:

  1. new Promise中 reject(),promise 状态由 pending 转为 rejected
  2. 执行.catch(),并return 'catch1 正常返回了',这里相当于 Promise.resolve('catch1 正常返回了')
  3. 执行.then(),打印 then1

    总结:.catch()回调正常返回,则返回值会传递给与之关联的promise

而在.catch()中抛出错误,则会被下一个.catch()捕获,如果没有再定义.catch()则错误会直接抛出

new Promise((resolve,reject) =>{
reject()
}).catch((err) =>{
throw new Error('ee')
}).then((res) =>{
console.log('then1:',res)
}).catch((err) =>{
console.log('catch2:',err)
})

结果:

catch2: Error: ee

例九

Promise.reject('start')
.catch((res) =>{
console.log('catch1',res)
return new Error('new error1')
}).then((res) =>{
console.log('then1',res)
return new Error('new error2')
}).then((res) =>{
console.log('then2',res)
}).catch((err) =>{
console.log('catch2',err)
})

结果:

catch1 start
then1 Error: new error1
then2 Error: new error2

分析:

  1. 从上往下执行start将会被.catch()捕获,并打印catch1 start
  2. 继续执行,.catch()返回一个promise包装的错误对象error1,注意这里是错误对象,会被当前一个对象来处理,而不是一个错误;相当于Promise.resolve(new Error('error1'));所以.catch()返回值会被紧接着的.then()方法接收到,并打印then1 Error: new error1
  3. 继续执行,.then()又返回了一个promise包装的错误对象error2,同样这里不会当成错误被最后的.catch()捕获,而是会被下一个.then()接收到,此时打印了then2 Error: new error2

总结:.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获

例十

const p = new Promise((resolve,reject) =>{
resolve(11)
}).then(() =>{
return p
})

结果:

Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

分析:.then().catch() 返回的值如果是 promise 本身,会造成死循环;就像无线回调,不停的往下注册、调用.then()

例十一

Promise.resolve('start')
.finally((res1) =>{
console.log('finally',res1)
return 'finally'
}).then((res2) =>{
console.log('then',res2)
}).finally(() =>{
throw new Error('error')
}).catch((res3) =>{
console.log('catch',res3)
})

结果:

finally undefined
then start
catch Error: error

分析:

  1. finally的回调函数接收不到promise的结果,所以res1返回的是undefined
  2. finally的返回值没有抛出错误的情况下默认是上一个promise的返回值,所以.then()方法接收的实际上是Promise.resolve('start')的返回值,打印的是start
  3. 然后.then()方法没有return任何值,此时会等同于Promise.resolve('undefined')
  4. 往下执行又碰到了finally,此时.finally()方法抛出了一个错误,会被下一个最近的.catch()方法捕获,所以res3打印了Error:error

例十二:串行promise

传入一个url组成的数组,按顺序执行数组中的url并返回结果,下面例子的fetchUrl函数为了测试可以设置setTimeout,而在实际开发中类似长这样:

function fetchUrl(url){
return fetch(url)
.then((response) =>{
return response.text()
}).then((val) =>{
arr.push(val)
})
}

promise实现

var urls = [1,2,3,4]
function fetchAll(urls){
let arr = []
let p = Promise.resolve(undefined)
function fetchUrl(url){
return new Promise((resolve,reject) =>{
setTimeout(() =>{
console.log(url)
resolve(url*2)
},1000)
})
}
for (let url of urls) {
p = p.then((function(url){
return () => fetchUrl(url)
})(url)).then((res) =>{
arr.push(res)
})
}
return p.then(() => arr)
}
fetchAll(urls).then((res) =>{
console.log(res)
})

分析:

  1. fetchAll里面定义fetchUrl函数,该函数会去调用url对应的接口,返回值会用promise包装,并将返回值保存在arr
  2. 这里需要注意的是,如果在循环中写 p = p.then(() => fetchUrl(url)),那么这个url将会是数组中的最后一个url
  3. 因为要p.then()中的参数回调是一个异步任务,在循环时参数回调会被放入微任务队列,不会立即执行,导致不能绑定正确的url
  4. 所以在for of循环中 p.then()的参数回调需要用立即执行表达式绑定每个url(或者用forEach循环,forEach循环的内部会给每个元素执行一遍回调函数)

    结果:
1
2
3
4
[2, 4, 6, 8]

async await 实现

var urls = [1,2,3,4]
function fetchUrl(url){
return new Promise((resolve,reject) =>{
setTimeout(() =>{
console.log(url)
resolve(url*2)
},1000)
})
}
async function awaitFetch(urls) {
let arr = []
for (let url of urls){
let p = await fetchUrl(url)
arr.push(p)
}
console.log(arr)
return arr
}
awaitFetch(urls)

分析:await 会等后面的fetch(url)返回的promise resolve()之后,才会执行后面的代码,在此之前,await阻塞之后的代码执行,所以这个for of循环相当于同步循环了

结果:

1
2
3
4
[2, 4, 6, 8]

生成器实现

var urls = [1,2,3,4]
function fetchUrl(url){
return new Promise((resolve,reject) =>{
setTimeout(() =>{
console.log(url)
resolve(url*2)
},1000)
})
}
async function* fetchGen(urls){
for (let url of urls){
let p = await fetchUrl(url)
yield p
}
}
async function fetchAllByGen(urls) {
let arr = []
for await(let p of fetchGen(urls)){
arr.push(p)
}
console.log(arr)
return arr;
}
fetchAllByGen(urls)

分析:

  1. for of 循环专门用于可迭代对象,而生成器就是一个迭代器对象。fetchGen是一个异步生成器函数
  2. for await 循环中,每次循环会等待 fetchGen(url)返回结果,再往下执行 arr.push(p)的操作
  3. 为了测试,将fetchUrl函数中的 fetch api 改成了 setTimeout

结果:

1
2
3
4
[2, 4, 6, 8]

12个例子夯实promise基础的更多相关文章

  1. 夯实Java基础系列1:Java面向对象三大特性(基础篇)

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...

  2. 夯实Java基础系列9:深入理解Class类和Object类

    目录 Java中Class类及用法 Class类原理 如何获得一个Class类对象 使用Class类的对象来生成目标类的实例 Object类 类构造器public Object(); register ...

  3. 夯实Java基础系列10:深入理解Java中的异常体系

    目录 为什么要使用异常 异常基本定义 异常体系 初识异常 异常和错误 异常的处理方式 "不负责任"的throws 纠结的finally throw : JRE也使用的关键字 异常调 ...

  4. 夯实Java基础系列14:深入理解Java枚举类

    目录 初探枚举类 枚举类-语法 枚举类的具体使用 使用枚举类的注意事项 枚举类的实现原理 枚举类实战 实战一无参 实战二有一参 实战三有两参 枚举类总结 枚举 API 总结 参考文章 微信公众号 Ja ...

  5. 【RL-TCPnet网络教程】第12章 TCP传输控制协议基础知识

    第12章      TCP传输控制协议基础知识 本章节为大家讲解TCP(Transmission Control Protocol,传输控制协议),通过本章节的学习,需要大家对TCP有个基本的认识,方 ...

  6. 夯实Java基础(十一)——内部类

    1.内部类的概念 内部类顾名思义:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类.对于很多Java初学者来说,内部类学起来真的是一头雾水,根本理解不清楚是个什么东西,包括我自己(我太菜 ...

  7. 夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!

    目录 目录 string基础 Java String 类 创建字符串 StringDemo.java 文件代码: String基本用法 创建String对象的常用方法 String中常用的方法,用法如 ...

  8. 夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理

    目录 final使用 final变量 final修饰基本数据类型变量和引用 final类 final关键字的知识点 final关键字的最佳实践 final的用法 关于空白final final内存分配 ...

  9. 夯实Java基础系列5:Java文件和Java包结构

    目录 Java中的包概念 包的作用 package 的目录结构 设置 CLASSPATH 系统变量 常用jar包 java软件包的类型 dt.jar rt.jar *.java文件的奥秘 *.Java ...

  10. 夯实Java基础系列6:一文搞懂抽象类和接口,从基础到面试题,揭秘其本质区别!

    目录 抽象类介绍 为什么要用抽象类 一个抽象类小故事 一个抽象类小游戏 接口介绍 接口与类相似点: 接口与类的区别: 接口特性 抽象类和接口的区别 接口的使用: 接口最佳实践:设计模式中的工厂模式 接 ...

随机推荐

  1. Codeforces Round #623 (Div. 2) A~D题,D题multiset使用

    比赛链接:Here 1315A. Dead Pixel 签到题, 比较四个值 max(max(x, a - 1 - x) * b, a * max(y, b - 1 - y)) 1315B. Home ...

  2. 「Codeforces 1131D」Gourmet Choice

    Description 美食家 Apple 先生是一家美食杂志的主编.他会用一个正整数来评价每一道菜. 美食家在第一天品尝第 $n$ 道菜,第二天品尝了 $m$ 道菜.他制作了一张 $n\times ...

  3. 2、springboot创建多模块工程

    系列导航 springBoot项目打jar包 1.springboot工程新建(单模块) 2.springboot创建多模块工程 3.springboot连接数据库 4.SpringBoot连接数据库 ...

  4. Webpack Vue瘦身,感受快到飞起的加载速度!

    症结 在利用webpack脚手架搭建vue项目后,往往最终打包的.js和.css文件过于庞大,造成首次加载的时候白屏时间过长,影响用户体验,下图为未经任何优化直接npm run build之后的情况: ...

  5. java进阶(7)--Object类-toString()/equals()/finalize()/hashCode()

    一.object类介绍 object类这个老祖宗中的方法,所有子类通用,直接或间接继承. 学习常用方法即可 列表 prtected object clone()             //对象克隆 ...

  6. GoLang 指针初探

    1. 内置类型和引用类型 Go 中内置类型包括数值类型,字符串类型和布尔类型.引用类型包括切片,映射,通道,接口和函数类型.其中,引用类型表示创建的变量包含一个指向底层数据结构的指针和一组管理底层数据 ...

  7. 使用React简短代码动态生成栅格布局

    使用React简短代码动态生成栅格布局 作为 TerminalMACS 的一个子进程模块 - React Web管理端,使用Ant Design Pro作为框架. 本文应用到的知识 1.样式文件les ...

  8. 15-TTL与非门

    TTL与非门 集成电路有两大类COMOS和TTL(三极管) 电路结构 工作原理 多发射结的三极管,两个输入之间是与的关系 输入低电平 输入高电平 A.B都是高电平 倒置放大 压差大的先导通 T3,T4 ...

  9. 如何使用Oracle Enterprise Manager Database Express连接到PDB数据库

    1.问题 1.1重复弹出登录框,无法登陆 关闭登录框,显示invalid container name 1.2 重启后PDB数据库处于mounted挂载状态,未打开导致使用 Enterprise 登陆 ...

  10. 【RTOS】基于RTOS的嵌入式系统看门狗策略

    RTOS - high integrity systems 看门狗策略 Watchdog Strategies for RTOS enabled embedded systems 介绍 看门狗定时器就 ...