靡不有初,鲜克有终
不积跬步无以至千里
一、Promise的理解和使用
1、Promise是什么?
1.1、理解
1.1.1、抽象表达:
-
Promise
是一门新的技术(ES6
规范正式纳入,2017年); -
Promise
是JS
中进行异步编程的新解决方案;旧方案关于异步操作是单纯使用回调函数,如果存在依赖过深的话,就会出现著名的回调地狱问题;
常见异步操作如下:
a、fs 文件操作
require('fs').readFile('./index.html', (err,data)=>{})
// 场景需求:依次读取1、2、3.txt文件的值,注意要求是依次
const fs = require('fs');
fs.readFile('./1.txt', 'utf8', (err, result1) => {
console.log(result1)
fs.readFile('./2.txt', 'utf8', (err, result2) => {
console.log(result2)
fs.readFile('./3.txt', 'utf8', (err, result3) => {
console.log(result3)
})
})
});
复制代码
b、数据库操作
c、Ajax
$.get('/server', (data)=>{})
d、定时器
setTimeout(()=>{}, 2000)
1.1.2、具体表达:
- 从语法上来说:
Promise
是一个构造函数,其实就是在原本的异步操作的外面再包裹一层promise
进行异步操作,见下面例一;
- 从功能上来说:
Promise
可以进行对象的实例化,而实例化的该对象可以用来封装一个异步操作,并且可以获取成功或者失败的结果值;
1.2、promise 的状态改变
1、pending
变为resolved
2、pending
变为 rejected
Note:
promise
只可能有三种状态:
等待(pending)、已完成(resolved/fulfilled)、已拒绝(rejected)
promise
的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换;
且状态只能改变一次,即改变之后无论变为成功还是失败, 都不会再改变;
then
方法接受两个参数,第一个参数是成功时的回调,在promise
由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise
由“等待”态转换到“拒绝”态时调用;
1.3、promise的基本流程
1.4、promise的基本使用
例一:基本操作
// 1) 创建 promise 对象(pending 状态), 指定执行器函数
const p = new Promise((resolve, reject) => {
// 2) 在执行器函数中启动异步任务
setTimeout(() => {
const time = Date.now()
// 假设: 时间为奇数代表成功, 为偶数代表失败
if (time % 2 === 1) {
resolve('成功的值 ' + time)
} else {
reject('失败的值' + time)
}
}, 2000)
})
// 3) 能 promise 指定成功或失败的回调函数来获取成功的 vlaue 或失败的 reason
// p.then(
// (value) => {
// // 成功的回调函数 onResolved, 得到成功的 vlaue
// console.log('成功的 value: ', value)
// },
// (reason) => {
// // 第二个参数
// // 失败的回调函数 onRejected, 得到失败的 reason
// console.log('失败的 reason: ', reason)
// }
// )
// --------------------还是更建议这种写法,方便直观-------------------
p.then((value) => {
// 成功的回调函数 onResolved, 得到成功的 vlaue
console.log('成功的 value: ', value)
}).catch((reason) => {
// 第二个参数
// 失败的回调函数 onRejected, 得到失败的 reason
console.log('失败的 reason: ', reason)
})
复制代码
例二:封装基于定时器的异步函数
function doDelay(time) {
// 1. 创建 promise 对象
return new Promise((resolve, reject) => {
// 2. 启动异步任务
console.log('启动异步任务')
setTimeout(() => {
console.log('延迟任务开始执行...')
const time = Date.now()
// 假设: 时间为奇数代表成功, 为偶数代表失败
if (time % 2 === 1) {
resolve('成功的数据 ' + time)
} else {
reject('失败的数据 ' + time)
}
}, time)
})
}
const promise = doDelay(2000)
promise
.then((value) => {
console.log('成功的 value: ', value)
})
.catch((reason) => {
console.log('失败的 reason: ', reason)
})
复制代码
例三:封装ajax
异步请求函数
Note:
ajax
请求本身就是异步操作;
/*
可复用的发 ajax 请求的函数: xhr + promise
*/
function promiseAjax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return
const { status, response } = xhr
// 请求成功, 调用 resolve(value)
if (status >= 200 && status < 300) {
resolve(JSON.parse(response))
} else {
// 请求失败, 调用 reject(reason)
reject(new Error('请求失败: status: ' + status))
}
}
xhr.open('GET', url)
xhr.send()
})
}
promiseAjax('https://api.apiopen.top2/getJoke?page=1&count=2&type=video')
.then((data) => {
console.log('显示成功数据', data)
})
.catch((error) => {
alert(error.message)
})
复制代码
2、 为什么要用 Promise?
2.1、指定回调函数的方式更加灵活
1、旧方案: 必须在启动异步任务前指定
2、promise
: 启动异步任务 => 返回promise
对象 => 给promise
对象绑定回调函数(甚至可以在异步任务结束后指定多个)
2.2、支持链式调用,可以解决回调地狱问题
2.2.1、什么是回调地狱?
回调函数嵌套调用, 内部函数执行条件(例如参数)依赖 外部回调函数异步执行的结果
2.2.2、 回调地狱的缺点?
不便于阅读,不便于异常处理,不易维护等
2.2.3、解决方案?
promise
链式调用
2.2.4、终极解决方案?
async/await
2.2.5、实例
例一、promise
解决回调地狱
const fs = require('fs');
// --------------回调地狱---------------------
// fs.readFile('./1.txt', 'utf8', (err, result1) => {
// console.log(result1)
// fs.readFile('./2.txt', 'utf8', (err, result2) => {
// console.log(result2)
// fs.readFile('./3.txt', 'utf8', (err, result3) => {
// console.log(result3)
// })
// })
// });
function p1 () {
return new Promise ((resolve, reject) => { // return一个promise对象
fs.readFile('./1.txt', 'utf8', (err, result) => {
resolve(result)
})
});
}
function p2 () {
return new Promise ((resolve, reject) => {
fs.readFile('./2.txt', 'utf8', (err, result) => {
resolve(result)
})
});
}
function p3 () {
return new Promise ((resolve, reject) => {
fs.readFile('./3.txt', 'utf8', (err, result) => {
resolve(result)
})
});
}
p1().then((r1)=> {
console.log(r1);
return p2();
})
.then((r2)=> {
console.log(r2);
return p3();
})
.then((r3) => {
console.log(r3)
})
复制代码
例二、asyncFunc
异步函数
1.在普通函数定义的前面加上async
关键字 普通函数就变成了异步函数;
2.异步函数默认的返回值是promise
对象,而不是单纯的undefined
;
3.在异步函数内部使用throw
关键字进行错误的抛出,代替promise
中的reject
;
另外,await
关键字:
1.它只能出现在异步函数async
中;
2.await (一个promise操作)
它可以暂停异步函数的执行 等待promise
对象返回结果后再向下执行函数;
async function fn() {
throw "发生了一些错误";
}
fn()
.then(function (data) {
console.log(data);
})
.catch(function (err) {
console.log(err);
});
// --------------------async----------------------
async function p1() {
return "p1";
}
async function p2() {
return "p2";
}
async function p3() {
return "p3";
}
console.log('我返回的不是单纯的字符串',p1());
async function run() {
let r1 = await p1();
let r2 = await p2();
let r3 = await p3();
console.log(r1);
console.log(r2);
console.log(r3);
}
run();
复制代码
Note:
1、因为异步函数async
默认的返回值是promise
对象,所以此处的p1、p2、p3函数的返回值不是单纯的字符串,而是promise
对象:
MDN官方:
2、
await
后面可以跟任何的JS 表达式
Note:
虽然说 await
可以等很多类型的东西,但是它最主要的意图是用来等待 Promise
对象;
如果await
后面是 promise
对象会造成异步函数停止执行并且等待 promise
的解决;
如果await
后面是 正常的表达式则按照常规代码立即执行。
例如:
例三、asyncFuncReadFile
const fs = require('fs');
// 改造现有异步函数api 让其返回promise对象 从而支持异步函数语法
const promisify = require('util').promisify;
// 调用promisify方法改造现有异步API 让其返回promise对象,从而使得await‘生效’
const readFile = promisify(fs.readFile);
async function run () {
let r1 = await readFile('./1.txt', 'utf8')
let r2 = await readFile('./2.txt', 'utf8')
let r3 = await readFile('./3.txt', 'utf8')
console.log(r1)
console.log(r2)
console.log(r3)
}
run();
复制代码
3、如何使用Promise?
3.1、API的使用
1、Promise
构造函数: Promise (excutor) {}
(1) executor
函数: 执行器(resolve, reject) => {}
(2) resolve
函数: 内部定义成功时我们调用的函数 value => {}
(3) reject
函数: 内部定义失败时我们调用的函数reason => {}
Note:
executor
会在Promise
内部立即同步调用,异步操作在执行器中执行
所以在我的这篇文章,传送门:JS的事件执行机制
涉及到的new Promise(function (resolve, reject) {...}
操作,默认属于同步执行代码
2、Promise.prototype.then
方法: (onResolved, onRejected) => {}
(1) onResolved
函数: 成功的回调函数 (value) => {}
(2) onRejected
函数: 失败的回调函数(reason) => {}
Note:
指定用于得到成功value
的成功回调和用于得到失败 reason
的失败回调,返回一个新的 promise
对象
3、Promise.prototype.catch
方法:(onRejected) => {}
(1) onRejected
函数: 失败的回调函数(reason) => {}
Note:
then()
的语法糖, 相当于: then(undefined, onRejected)
4、Promise.resolve
方法: (value) => {}
(1)value
: 成功的数据或promise
对象
Note:
如果传入的参数为 非Promise
类型的对象, 则返回的结果默认为成功的promise对象
;
如果传入的参数为 Promise
对象, 则参数的结果决定了 resolve
的结果;
let p1 = Promise.resolve(123)
console.log('p1', p1)
let p2 = Promise.resolve(
new Promise((resolve, reject) => {
// resolve('OK')
reject('Error')
})
)
console.log('p2', p2)
p2.catch((reason) => {
console.log(reason)
})
复制代码
得到:
5、Promise.reject
方法: (reason) => {}
(1) reason
: 失败的原因
说明:
返回一个失败的promise
对象,注意和第四点Promise.resolve
方法的区别
let p1 = Promise.reject(123)
let p2 = Promise.reject('abc')
let p3 = Promise.reject(
new Promise((resolve, reject) => {
resolve('OK')
})
)
console.log(p1)
console.log(p2)
console.log(p3)
复制代码
得到:
6、Promise.all
方法:(promises) => {}
(1) promises
: 包含 n 个 promise
的数组
Note:
返回一个新的promise
, 只有所有的 promise
都成功才成功;
a、都成功的情况
let p1 = new Promise((resolve, reject) => {
resolve('OK')
})
let p2 = Promise.resolve('Success')
let p2 = Promise.resolve('Oh Yeah')
const result = Promise.all([p1, p2, p3])
console.log(result)
复制代码
得到:
b、有失败的情况
Note:
都成功的话就返回数组,有失败的情况结果还是,以第一个失败的值为准
let p1 = new Promise((resolve, reject) => {
console.log(1111111)
resolve('OK')
})
let p2 = new Promise((resolve, reject) => {
console.log(222222)
reject('失败了2222')
})
let p3 = new Promise((resolve, reject) => {
console.log(3333333)
reject('失败了3333')
})
const result = Promise.all([p1, p2, p3])
console.log(result)
复制代码
得到:
7、Promise.race
方法: (promises) => {}
(1) promises
: 包含 n 个 promise
的数组
Note:
返回一个新的promise
, 第一个完成的 promise 的结果状态就是最终的结果状态
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 1000)
})
let p2 = Promise.resolve('Success')
let p3 = Promise.resolve('Oh Yeah')
//调用
const result = Promise.race([p1, p2, p3])
console.log(result)
复制代码
得到:
3.2、promise 的几个关键问题
1、 如何改变promise
的状态?
(1) resolve(value)
: 如果当前是 pending
就会变为 resolved
(2) reject(reason)
: 如果当前是 pending
就会变为rejected
(3) 抛出异常: 如果当前是 pending
就会变为rejected
let p = new Promise((resolve, reject) => {
//1. resolve 函数
// resolve('ok') // pending => fulfilled (resolved)
//2. reject 函数
// reject('error') // pending => rejected
//3. 抛出错误
throw '出问题了' // pending => rejected
})
console.log(p)
复制代码
Note:
状态改变后then、catch
才会执行;
2、一个promise
指定多个成功/失败回调函数, 都会调用吗?
Note:
当promise
改变为对应状态时都会调用,如下:
let p = new Promise((resolve, reject) => {
resolve('OK')
})
///指定回调 - 1
p.then((value) => {
console.log(111, value)
})
//指定回调 - 2
p.then((value) => {
console.log(222, value)
})
复制代码
得到:
3、改变 promise
状态(上面三种方式)和指定回调函数(then)谁先谁后?
(1) 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
(2) 如何先改状态再指定回调?
① 在执行器中直接调用 resolve()/reject()
② 延迟更长时间才调用then()
(3) 什么时候才能得到数据?
① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
② 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
说简单点:
其实就是同步代码、微任务、宏任务的执行顺序区别;
传送门:JS的事件执行机制
3.1、当Promise
内部改变状态的操作(resolve, reject, throw
),不包含在操作异步里面时
let p = new Promise((resolve, reject) => {
console.log(1) // 同步
resolve('OK') // 微任务
console.log(22) // 同步
})
p.then(
(value) => {
console.log(value)
},
(reason) => {}
)
复制代码
得到:
3.2、当Promise
内部改变状态的操作(resolve, reject, throw
),包含在操作异步里面时
let p = new Promise((resolve, reject) => {
console.log(111) // 同步
setTimeout(() => { // 异步宏任务
console.log(222) // 异步宏任务 --- 同步任务
resolve('OK') // 异步宏任务 --- 微任务
console.log(333) // 异步宏任务 --- 同步任务
}, 1000)
console.log(444) // 同步
})
p.then(
(value) => {
console.log(value)
},
(reason) => {}
)
复制代码
得到:
4、promise.then()
返回的新promise
的结果状态由什么决定?
(1) 简单表达:
由 then()
指定的回调函数执行的结果决定
(2) 详细表达:
① 如果抛出异常, 新promise
的状态变为rejected
② 如果返回的是非 promise
的任意值, 新 promise
的状态 自动默认为 resolved
③ 如果返回的是另一个新 promise
,此 promise
的结果就会成为新promise
的结果
let p = new Promise((resolve, reject) => {
resolve('ok')
})
//执行 then 方法
let result = p.then(
(value) => {
// console.log(value);
//1. 抛出错误------失败的状态reject
// throw '出了问题'
//2. 返回结果是非 Promise 类型的对象------自动变为成功
// return 521
//3. 返回结果是 Promise 对象----以新返回的为准
return new Promise((resolve, reject) => {
// resolve('success');
reject('error')
})
},
(reason) => {
console.log('注意一下,前面的失败和我这里毫无关系')
console.warn(reason)
}
)
console.log(result)
复制代码
5、promise
如何串连多个操作任务?
(1) promise
的 then()返回一个新的 promise, 可以写成成then()
的链式调用
(2) 通过then
的链式调用串连多个同步/异步任务
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 1000)
})
p.then((value) => {
console.log(value) // 一秒钟之后打印 Ok
return new Promise((resolve, reject) => {
resolve('success')
})
})
.then((value) => { // 前面的then返回一个新的 promise, 可以开成then()的链式调用
console.log(value) // success
})
.then((value) => { // 前面的then无返回值
console.log(value) // undefined 如果前面无返回值,即默认返回undefined
})
复制代码
6、 promise
异常穿透?
(1) 当使用 promise
的then
链式调用时, 可以在最后指定失败的回调,
(2) 前面任何操作出了异常, 都会传到最后失败的回调中处理
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
// reject('Err')
}, 1000)
})
p.then((value) => {
console.log(value)
throw '失败啦!'
})
.then((value) => {
console.log(222)
})
.then((value) => {
console.log(333)
})
.catch((reason) => {
console.log('执行了catch')
console.log(reason)
})
复制代码
得到:
7、中断promise
链 ?
(1) 当使用promise
的then
链式调用时, 在中间中断, 不再调用后面的回调函数
(2) 办法有且只有一个: 在回调函数中返回一个 pendding
状态的promise
对象
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 1000)
})
p.then((value) => {
console.log(111) // 只会打印 111
//有且只有一个方式: 返回pending,中断promise链 (前面说过,只有状态改变了才会执行)
return new Promise(() => {})
})
.then((value) => {
console.log(222) // 已经中断,不会打印
})
.then((value) => {
console.log(333) // 已经中断,不会打印
})
.catch((reason) => {
console.warn(reason) // 已经中断且无报错,不会打印
})
复制代码
二、Async / Await
1、知识点:
a、async
关键字在函数声明前使用,例如:async function test(){}
b、async
函数总是返回一个Promise
对象,不论函数是否 return Promise
对象,例如:
async function main() {
//1. 如果返回值是一个非Promise类型的数据
return 123
}
let result = main()
console.log(result) // 实际上是一个promise对象
复制代码
得到:
async function main() {
//2. 如果返回的是一个Promise对象
return new Promise((resolve, reject) => {
resolve('OK');
})
}
复制代码
得到:
async function main() {
//3. 抛出异常
throw 'Oh NO'
}
let result = main()
console.log(result)
复制代码
得到:
c、await
只能用在 async
函数中,用于其它地方会直接报错
d、async/await
和Primose
对象在本质上是一样的
e、async/await
作用是用同步方式,执行异步操作
f、如果await
的promise
失败了, 就会抛出异常, 需要通过try...catch
捕获处理
2、解决了什么问题?
Promise.then
语法已经解决了以前一直存在的多层回调嵌套的问题,那问什么还要用async/await
呢?先来看一段Promise
代码:
function login () {
return new Promise(resolve => {
resolve('aaaa')
})
}
function getUserInfo (token) {
return new Promise(resolve => {
if (token) {
resolve({
isVip: true
})
}
})
}
function getVipGoods (userInfo) {
return new Promise(resolve => {
if (userInfo.isVip) {
resolve({
id: 'xxx',
price: 'xxx'
})
}
})
}
function showVipGoods (vipGoods) {
console.log(vipGoods.id + '----' + vipGoods.price)
}
login()
.then(token => getUserInfo(token))
.then(userInfo => getVipGoods(userInfo))
.then(vipGoods => showVipGoods(vipGoods))
复制代码
如例子所示,每一个Promise
相当于一个异步的网络请求,通常一个业务流程需要多个网络请求,而且网络请求网络请求都依赖一个的请求结果,上例就是Promise
模拟了这个过程,再来看看用async/await
会有什么不同:
async function call() {
const token = await login()
const userInfo = await getUserInfo(token)
const vipGoods = await getVipGoods(userInfo)
showVipGoods(vipGoods)
}
call()
复制代码
和Promise
的then
链调用相比,async/await
的调用更加清晰简单,和同步代码一样
3、值得注意的地方
使用async/await
我们经常会忽略一个问题,同步执行带来的时间累加,有时候我们的代码可以写成并发执行,但是由于async/await
做成了继发执行,来看一个例子:
function test () {
return new Promise(resolve => {
setTimeout(() => {
console.log('test')
resolve()
}, 1000)
})
}
function test1 () {
return new Promise(resolve => {
setTimeout(() => {
console.log('test1')
resolve()
}, 1000)
})
}
function test2 () {
return new Promise(resolve => {
setTimeout(() => {
console.log('test2')
resolve()
}, 1000)
})
}
async function call () {
await test()
await test1()
await test2()
}
call () // 大概需要花3000ms。。。
复制代码
实际上,如果这段代码执行顺序我并不关心,继发执行就浪费大量执行时间,下面改成并发执行:
function call () {
Promise.all([test(), test1(), test2()])
}
call() // 大概1000ms就好了,并行执行。。。
复制代码
4、看个栗子?
// ----------------------------------------------------案例一
async function async() {
return { a: 123, b: 456 }
}
async function testFunc() {
console.log('顺序1')
const promise = async()
console.log('顺序2---async函数默认返回promise对象', promise)
const value = await async() // 相当于使用then包含后面的所有操作,见下
console.log('顺序3', value)
console.log('顺序4')
}
testFunc()
// ----------------------------------------------------案例二
async function async() {
return { a: 123, b: 456 }
}
async function testFunc() {
console.log('顺序1')
const promise = async()
console.log('顺序2---async函数默认返回promise对象', promise)
Promise.resolve(async()).then((val) => {
const value = val
console.log('顺序3', value)
console.log('顺序4')
})
}
testFunc()
复制代码
都会得到一样的结果:
5、自己怎么实现 async/await 的原理?
因为async/await
本身就是promise+generator
的语法糖,使用ES6
里的迭代函数generator
也可以实现一样的效果,具体细节,可以看一下大佬的这篇文章:
7张图,20分钟就能搞定的async/await原理!为什么要拖那么久?
🚀🚀🚀
建议: 看完这篇文章后,再看一下这两篇文章会更香!