ES6引入的进行异步编程的解决方案,从语法上说它是一个构造函数。
异步编程包括但不限于:文件操作、数据库操作、AJAX、定时器
为什么要用Promise?
之前进行异步编程直接通过回调函数的方式进行,会导致回调地狱。
- 回调函数:作为参数的函数
- 回调地狱:回调函数里面嵌套回调函数
具体理解:
从语法上说,Promise是一个构造函数
从功能上说,Promise对象用来封装一个异步操作并可以获取其成功/失败的结果值
1 Promise的初体验
实现:
点击按钮, 1s 后显示是否中奖(30%概率中奖)
若中奖弹出 恭喜您中奖
若未中奖弹出 谢谢参与
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="container">
<h2 class="page-header">Promise初体验</h2>
<button class="btn btn-primary" id="btn">点击抽奖</button>
</div>
<script type="text/javascript">
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}
// 点击按钮,2s后显示是否中奖(30%概率中奖)
// 若中奖弹出恭喜恭喜,奖品为10万 RMB 劳斯莱斯优惠券;若未中奖弹出再接再厉
// 获取元素对象
const btn = document.querySelector('#btn');
// 绑定单机事件
btn.addEventListener('click', function () {
// 30%
// 获取 1 - 100随机数
// setTimeout(() => {
// let n = rand(1, 100)
// if (n <= 30) {
// alert('恭喜恭喜,奖品为10万 RMB 劳斯莱斯优惠券')
// } else {
// alert('再接再厉')
// }
// }, 1000)
// resolve 解决 - 函数类型的数据 - success
// reject 拒绝 - 函数类型的数据 - fail
const p = new Promise((resolve, reject) => {
// Promise 对象 - 可以包裹一个异步任务
setTimeout(() => {
let n = rand(1, 100)
if (n <= 30) {
resolve(n) // 将 promise 对象的状态设置为 『成功』
} else {
reject(n) // 将 promise 对象的状态设置为 『失败』
}
}, 1000);
})
// 调用then方法
// 成功就调用第一个回调函数,失败调用第二个回调
p.then((value) => {
alert('恭喜您中奖,您的中奖号码为' + value);
}, (reason) => {
alert('谢谢参与,您的号码为' + reason);
})
})
</script>
</body>
</html>
Promise构造函数的参数是一个包含异步操作的函数
new Promise((resolve, reject) => {})
该函数有两个参数,resolve和reject,这两个参数同样也是函数形式
- resolve在函数内的异步操作成功时调用,会将Promise对象状态设置为成功,并且将成功的结果传递给对象,供then的回调函数使用。
- reject在函数内的异步操作失败时调用,会将Promise对象状态设置为失败,并且将失败的原因传递给对象,供then的回调函数使用。
const p = new Promise((resolve, reject) => {
let n = rand(1, 100)
if (n <= 30) {
resolve(n) // 将 promise 对象的状态设置为 『成功』
} else {
reject(n) // 将 promise 对象的状态设置为 『失败』
}
p.then((value) => {
alert('恭喜您中奖,您的中奖号码为' + value);
}, (reason) => {
alert('谢谢参与,您的号码为' + reason);
})
Promise.then()函数具有两个参数,都是函数形式
- 第一个回调函数在Promise状态为成功时调用
其参数value是Promise的构造函数中回调函数中成功的结果 - 第二个回调函数在Promise状态为失败时调用
其参数reason是Promise的构造函数中回调函数中失败的原因
2 fs读取文件
基础回调函数形式:
const fs = require('fs')
fs.readFile('D:\\code\\wt-promise\\src\\components\\HelloWorld.vue', (err, data) => {
if (err) {
throw err;
} else {
console.log(data.toString())
}
})
- readFile的第一个参数是文件路径
- 第二个参数是一个回调函数,err表示出错信息,data表示获取到的数据
- 函数主体则根据参数进行操作即可
Promise封装后:
const fs = require('fs')
let p = new Promise((resolve, reject) => {
fs.readFile('D:\\code\\wt-promise\\src\\components\\HelloWorld.vue', (err, data) => {
// 如果出错
if (err) reject(err);
// 如果成功
resolve(data);
});
});
p.then(value => {
console.log(value.toString());
}, reason => {
console.log(reason);
})
3 AJAX请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div cLass="container">
<h2 class="page-header">Promise封装AJAX 操作</h2>
<button class="btn btn-primary" id="btn">点击发送AJAX</button>
</div>
<script>
//接口地址 https://api.apiopen.top/getJoke
//获取元素对象
const btn = document.querySelector('#btn');
btn.addEventListener('click', function () {
// 1.创建对象
const xhr = new XMLHttpRequest();
// 2.初始化
xhr.open('GET', 'https://www.baidu.com/');
// 3.发送
xhr.send();
// 4.处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 判断响应状态码
if (xhr.status >= 200 && xhr.status < 300) {
// 控制台输出响应体
console.log(xhr.response);
} else {
// 控制台输出响应体
console.log(xhr.status);
}
}
}
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div cLass="container">
<h2 class="page-header">Promise封装AJAX 操作</h2>
<button class="btn btn-primary" id="btn">点击发送AJAX</button>
</div>
<script>
//接口地址 https://api.apiopen.top/getJoke
//获取元素对象
const btn = document.querySelector('#btn');
btn.addEventListener('click', function () {
// 创建Promise对象
const p = new Promise((resolve, reject) => {
// 1.创建对象
const xhr = new XMLHttpRequest();
// 2.初始化
xhr.open('GET', 'https://api.apiopen.top/getJoke');
// 3.发送
xhr.send();
// 4.处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 判断响应状态码
if (xhr.status >= 200 && xhr.status < 300) {
// 控制台输出响应体
resolve(xhr.response);
} else {
// 控制台输出响应体
reject(xhr.status);
}
}
}
});
p.then(value => {
console.log(value);
}, reason => {
console.warn(reason);
});
});
</script>
</body>
</html>
4 Promise封装fs读取文件操作【真没发现!!!!】
封装一个函数 mineReadFile 读取文件内容
- 参数: path 文件路径
- 返回: promise 对象
function mineReadFile(path) {
return new Promise((resolve, reject) => {
// 读取文件
require('fs').readFile(path, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
其返回一个Promise对象
调用函数,使用Promise.then()的方法
mineReadFile('./resource/content.txt')
.then((value) => {
console.log(value.toString());
}, (reason) => {
console.log(reason);
});
-
原始需要在readFile的参数里面指定回调函数
-
使用Promise后,可以在mineReadFIle后面的then函数中指定回调函数,解决回调地狱问题
5 util.promisify方法进行promise风格转化
作用:自动将异步操作封装成一个Promise
// 引入util模块
const util=require('util');
// 引入fs模块
const fs=require('fs');
//返回一个新的函数
let mineReadFile=util.promisify(fs.readFile);
mineReadFile('./resource/content.txt').then(value=>{
console.log(value.toString());
});
7 Promise对象状态属性介绍
状态:实例对象中的一个属性 【PromiseState】,三个值
- pending 待定
- resolved / fullfilled 成功
- rejected 失败
状态改变: - 只能由pending改成resolved 或 pending改成rejected
- 状态只能改变一次
实例对象中的另一个属性 【PromiseResult】- 对象的值
保存着异步任务『成功/失败』的结果
- resolve
- reject
- 只有resolve、reject可以修改其值
8 Promise工作流程
- 调用构造函数前,Promise的状态为pending
- 调用构造函数,执行异步操作
成功则执行resolved(),修改Promise对象状态为resolved,在then函数中回调onResolved,返回一个新的Promise对象
失败则执行rejected(),修改Promise对象状态为rejected,在then函数中回调onRejected,返回一个新的Promise对象
9 Promise的API
1 构造函数-then-catch
- Promise构造函数:Promise(executor){}
executor函数:执行器函数 (resolve,reject)=>{ }
resolve函数:{ } 内部异步操作成功时我们调用的函数 value => { }
reject函数:{ } 内部异步操作失败时我们调用的函数 reason => { }
PS!!!
executor会在Promise内部立即同步调用,意味着代码执行到Promise的对象创建时就会执行其构造函数的回调函数,再执行下面的代码:
如这段代码先输出111再输出222
而执行器的异步操作,就在执行器中按照规定的异步顺序执行
- Promise.prototype.then()方法: (onResolved,onRejected)=>{ }
Promise.prototype.then()的prototype表示then是Promise的实例对象的方法,如果没有prototype则表示then是promise函数对象的方法(类似于静态方法)
参数为两个回调函数: onResolved,onRejected
onResolved函数:成功的回调函数:(value)=>{ }
onRejected函数:失败的回调函数:(reason)=>{ }
- Promise.prototype.catch()方法:(onRejected)=>{ }
只指定异步操作失败的回调函数,参数只有onRejected
onRejected函数:失败的回调函数:(reason)=>{ }
- Promise.resolve方法:(value)=>{ }
resolve是Promise类的静态方法,不需要实例就能调用
作用:接受一个参数,直接返回一个成功或失败的Promise对象,快速将一个值封装成一个Promise对象
特点:
如果传入的参数为非Promise对象,则返回的结果为状态为成功的Promise对象
如果传入的参数为Promise对象,则返回的结果为状态为传入的Promise对象的状态,返回结果的值为传入Promise的值
-
这边会报错是因为传入的Promise状态为rejected,这需要Promise有处理错误的回调函数,通过
P2.catch((reason)=>{} )
补上回调函数即可 -
相反,如果传入的Promise的状态为resolved,则不需要特意补一个回调函数也不会报错
-
Promise.reject()方法:(reason)=>{}
作用:返回一个值为reason的,状态为rejected的Promise对象
无论传入什么都返回一个状态为失败Promise,即使传入一个成功的Promise对象,返回的Promise的值是传入的成功的Promise对象,但是返回Promise的状态还是失败
- Promise.all方法:(promises)=>{}
作用:返回一个新的Promise,当所有Promise都成功,返回的Promise状态为成功,其结果为所有Promise的结果组成的一个数组;当存在失败的Promise,返回的Promise的状态为失败,其结果为失败的Promise的结果
成功情况:
失败情况
- Promise.race()方法 (promises) => {}
promises:包含 n 个 promise 的数组
注:返回一个新的 promise, 第一个完成的 promise 的结果状态就是最终的结果状态
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
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);
</script>
</body>
</html>
10 Promise关键问题
1 如何修改对象的状态
三种方式
- 在构造函数中直接调用 resolve 函数
- 在构造函数中直接调用 reject 函数
- 在构造函数中直接抛出错误:throw Error
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
// 1. resolve 函数
// resolve('ok'); // pending => fulfilled (resolved)
//2. reject 函数
// reject("error");// pending => rejected
//3. 抛出错误
throw '出问题了';
});
console.log(p);
</script>
</body>
</html>
2 一个Promise指定多个成功/失败的回调函数,都会调用吗
当promise状态改变为对应状态时都会调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
resolve('OK');
});
//指定回调 - 1
p.then(value => {
console.log(value);
});
//指定回调 - 2
p.then(value => {
alert(value);
});
</script>
</body>
</html>
3 改变Promise状态和指定回调函数谁先谁后
问题简化:Promise代码运行时,resolve先执行还是then先执行
注意,指定回调和执行回调函数是不一样的,指定回调类似于p.then()中声明有哪些回调函数,执行回调则是在状态确定之后执行之前指定的回调
两种情况都有可能
- 先改状态(执行reslove reject )再指定回调:
执行器中是一个同步操作,那么就先做执行器中的操作`再执行then语句即指定回调`
例如直接在执行器中直接调用 resolve()/reject()
执行器中是一个异步操作,延长更长时间才调用then()同样可以达到效果
- 先指定回调再改变状态
执行器中是异步操作时,会先执行then - 指定回调(但是不执行回调),再回头根据异步操作的顺序进行处理
什么时候才能得到数据? - 回调函数什么时候执行
- 如果先改变状态,那么当指定回调时,回调函数就会被调用,得到数据
- 如果先指定回调,那么当状态发生改变时,回调函数才被调用得到数据
如下为同步:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
resolve('OK');
});
p.then(value => {
console.log(value);
}, reason => {
})
</script>
</body>
</html>
如下为异步,需等一秒之后执行:
promise执行器中是一个异步任务,那么就执行then,指定回调;处理执行器中的异步任务,1000ms后改变状态;再执行成功的回调函数,得到数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(value);
}, reason => {
})
</script>
</body>
</html>
4 then()方法返回的Promise的对象的状态由什么决定
then方法返回结果由 then()指定的回调函数执行的结果决定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
resolve('ok');
});
// 执行 then 方法
let result = p.then(value => {
}, reason => {
});
console.log(result);
</script>
</body>
</html>
① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
resolve('ok');
});
// 执行 then 方法
let result = p.then(value => {
//1. 抛出错误
throw '出了问题';
}, reason => {
});
console.log(result);
</script>
</body>
</html>
② 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
resolve('ok');
});
// 执行 then 方法
let result = p.then(value => {
//2. 返回结果是非 Promise 类型的对象
return 521;
}, reason => {
// console.warn(reason);
});
console.log(result);
</script>
</body>
</html>
③ 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
resolve('ok');
});
// 执行 then 方法
let result = p.then(value => {
//3. 返回结果是 Promise 对象
return new Promise((resolve, reject) => {
// resolve('success');
reject('error');
});
}, reason => {
// console.warn(reason);
});
console.log(result);
</script>
</body>
</html>
5 Promise如何串联多个操作任务
(1) promise 的 then()返回一个新的 promise, 可以看成 then()的链式调用
(2) 通过 then 的链式调用串连多个同步/异步任务
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
// 第一次执行异步任务
setTimeout(() => {
console.log("first mission")
resolve("1000ms later resolve");
}, 1000)
})
p.then(
// 返回一个执行异步任务的Promise,从而第二次执行异步任务,再第一次执行完之后才能执行,链式调用避免回调地狱
(value) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("second mission")
resolve();
}, 1000)
})
},
).then(
value => {
return new Promise(resolve => {
setTimeout(() => {
console.log("third mission")
resolve();
}, 1000)
})
}
)
</script>
</body>
</html>
6 异常穿透
(1) 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,
(2) 前面任何操作出了异常, 都会传到最后失败的回调中处理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(111);
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => { // 在最后指定一个失败的回调
console.warn(reason);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
// reject('Err');
}, 1000);
});
p.then(value => {
// console.log(111);
throw '失败啦!';
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => { // 在最后指定一个失败的回调
console.warn(reason);
});
</script>
</body>
</html>
7 如何中断promise链
- 链式调用过程中,我们希望在某一个then()方法的onResolve回调函数调用过程中,中断Promise的链式调用,只需要返回一个状态为pending的Promise对象
当没有明确返回一个promise对象,then()也会返回一个Promise对象,状态为【resolved】,结果为【underfined】
- 因为then方法的回调函数调用依据是Promise的状态是resolved或者rejected,没有pending对应的回调函数,所以后面的都没法执行了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(111);
//有且只有一个方式
return new Promise(() => {
});
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
</script>
</body>
</html>
11 async
- async是一个函数修饰符
- 函数的返回值是Promise对象
- Promise对象的结果由async函数执行的返回值决定
和then一样:分成三种情况执行返回值: 是非Promise对象,是一个Promise对象,是抛出异常
12 await
获取Promise的成功的结果
- await右侧表达式一般为Promise对象,但也可以是一个其他值
- 如果表达式时Promise对象,await返回的Promise成功的值
- 如果表达式是其他值,直接将此值作为await的返回值
await 必须写在 async 函数中, 但 async 函数中可以没有 await
如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="container">
<h2 class="page-header">Promise初体验</h2>
<button class="btn btn-primary" id="btn">点击抽奖</button>
</div>
<script type="text/javascript">
async function main() {
let p = new Promise((resolve, reject) => {
// resolve('OK');
reject('Error');
})
//1. 右侧为promise的情况
// let res = await p;
//2. 右侧为其他类型的数据
// let res2 = await 20;
//3. 如果promise是失败的状态
try {
let res3 = await p;
} catch (e) {
console.log(e);
}
}
main();
</script>
</body>
</html>
13 async与await结合实践
//回调函数的方式
fs.readFile('D:\\code\\wt-promise\\src\\hello.text', (err, data1) => {
if (err) throw err;
fs.readFile('D:\\code\\wt-promise\\src\\hello.text', (err, data2) => {
if (err) throw err;
fs.readFile('D:\\code\\wt-promise\\src\\hello.text', (err, data3) => {
if (err) throw err;
console.log(data1 + data2 + data3);
});
});
});
//async 与 await
async function main() {
try {
// 如果表达式时Promise对象,await返回的Promise成功的值
let data1 = await mineReadFile('D:\\code\\wt-promise\\src\\hello.text');
let data2 = await mineReadFile('D:\\code\\wt-promise\\src\\hello.text');
let data3 = await mineReadFile('D:\\code\\wt-promise\\src\\hello.text');
console.log(data1 + data2 + data3);
} catch (e) {
console.log(e.code);
}
}
main();