Promise概念
promise是是一门新的技术(ES6规范),是JS中解决异步编程的新方案。(旧方案是单纯使用回调函数)。
从语法上来说,promise是一个构造函数,从功能上来说,Promise对象用来封装一个异步操作并可以获取成功或失败的结果值。
异步编程:fs文件操作,数据库操作,AJAX,定时器。
Promise支持链式调用,可以解决回调地狱问题。
回调地狱问题:
多层回调函数的相互嵌套,就形成了回调地狱。如图示例:
缺点:不利于阅读,不便于异常处理。
promise指定回调函数的方式更加灵活
-
旧的:必须在启动异步任务前指定‘
-
promise: 启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定多个)
Promise初体验
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="container">
<h2>初体验</h2>
<button id="btn">点击抽奖</button>
</div>
<script>
// 生成随机数
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1)) + m - 1
}
// 点击抽奖,2s后显示是否中奖,中奖概率为30%,
// 若中奖弹出,恭喜中奖
// 若未中奖,弹出再接再厉
// 获取按钮元素
let btn = document.getElementById('btn')
// 绑定单机事件
btn.addEventListener('click', function () {
// 回调函数形式实现
// setTimeout(()=>{
// let n = rand(1,100)
// // 30%的概率
// if(n <= 80){
// alert('恭喜中奖了')
// }else{
// alert('再接再励')
// }
// },1000)
// promise形式实现
// promise实例化时要接收一个参数,是函数类型的值,
// 这个函数还有两个形参,分别是resolve(解决)和reject(拒绝),也是函数类型的数据
// 当异步任务成功时候调用resolve,当异步任务失败时调reject.
// promise可以包裹一个异步操作
const p = new Promise((resolve, reject) => {
setTimeout(() => {
let n = rand(1, 100)
// 30%的概率
if (n <= 80) {
resolve() //调完后可将promise对象的状态设为成功
} else {
reject() //调完后可将promise对象的状态设置为失败
}
}, 1000)
})
// 状态失败和成功后要做的事可以放在这个promise对象的then方法里
// 这个then方法可以接收两个函数类型的参数,第一个是对象成功时的回调,第二个是对象失败的回调
p.then(() => { alert('恭喜中奖了') }, () => { alert('再接再励') })
})
</script>
</body>
</html>
Promise初体验2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="container">
<h2>初体验</h2>
<button id="btn">点击抽奖</button>
</div>
<script>
// 生成随机数
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1)) + m - 1
}
// 点击抽奖,2s后显示是否中奖,中奖概率为30%,
// 若中奖弹出,恭喜中奖
// 若未中奖,弹出再接再厉
// 获取按钮元素
let btn = document.getElementById('btn')
// 绑定单机事件
btn.addEventListener('click', function () {
// 回调函数形式实现
// setTimeout(()=>{
// let n = rand(1,100)
// // 30%的概率
// if(n <= 80){
// alert('恭喜中奖了')
// }else{
// alert('再接再励')
// }
// },1000)
// promise形式实现
// promise实例化时要接收一个参数,是函数类型的值,
// 这个函数还有两个形参,分别是resolve(解决)和reject(拒绝),也是函数类型的数据
// 当异步任务成功时候调用resolve,当异步任务失败时调reject.
// promise可以包裹一个异步操作
const p = new Promise((resolve, reject) => {
setTimeout(() => {
let n = rand(1, 100)
// 50%的概率
if (n <= 50) {
resolve(n) //调完后可将promise对象的状态设为成功
} else {
reject(n) //调完后可将promise对象的状态设置为失败
}
}, 1000)
})
// 状态失败和成功后要做的事可以放在这个promise对象的then方法里
// 这个then方法可以接收两个函数类型的参数,第一个是对象成功时的回调,第二个是对象失败的回调
p.then((value) => { alert(`恭喜中奖号码为${value}`) }, (reason) => { alert(`再接再励${reason}`) })
// 需求增进:把那个n也显示在alert弹出的内容中
// promise除了封装异步任务之外,还可以获取异步任务当中成功和失败的结果值(借助resolve和reject)
})
</script>
</body>
</html>
Promise实践练习--fs模块
const fs = require('fs')
// 回调函数的形式
// fs.readFile('./resource/content.txt',(err,data) => {
// //如果出错,则抛出错误
// if(err) throw err
// //输出文件内容
// console.log(data.toString());
// })
// Promise形式
let p = new Promise((resolve,reject) => {
fs.readFile('./resource/content.txt',(err,data) => {
//如果失败
if(err) reject(err)
// 如果成功
resolve(data)
})
})
// 调用p的then方法
p.then((value)=>{
console.log(value.toString())
},(reason)=>{
console.log(err)
})
Promise实践练习--AJAX
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Promise封装AJAX</h1>
<button id="btn">点击发送AJAX</button>
<script>
let btn = document.querySelector('#btn')
btn.addEventListener('click', function () {
const p = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('get', 'https://api.apiopen.top/getJok')
xhr.send()
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>
Promise封装fs读取文件操作
// 封装一个函数mineReadFile读取文件内容
// 参数:path 文件路径
// 返回:Promise对象
// 封装函数
function mineReadFile(path) {
return new Promise((resolve, reject) => {
require('fs').readFile(path, (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
// 读取文件
mineReadFile('./resource/content.txt').then(value => { console.log(value.toString()) }, reason => { console.log(err) })
util模块内置的promisify方法进行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())})
Promise封装AJAX请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 封装一个函数 sendAJAX 发送 GET AJAX请求
// 参数 url
// 返回结果 Promise对象
function sendAJAX(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.responseType = 'json'
xhr.open('GET', url)
xhr.send()
xhr.onreadystatechange() = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
} else {
reject(xhr.status)
}
}
}
})
}
sendAJAX('https://api.apiopen.top/getJoke').then(
value => {
console.log(value)
},
reason => {
console.log(reason)
}
)
</script>
</body>
</html>
promise对象状态属性介绍
promise的状态是实例对象中的一个内置属性,【PromiseState】,有三个值
- pending 未决定的
- resolved/fullfilled 成功
- rejected 失败
promise的状态改变
- pending 变为 resolved
- pending 变为 rejected
只有这两种改变,且一个promise对象只能改变一次,无论变为成功或失败,都会有一个结果数据,成功的结果一般称为value,失败的结果一般称为reason
promise对象结果值属性介绍
PromiseResult是promise对象结果属性值,保存着异步任务【成功/失败】的结果。 只有resolve和reject函数可以这个属性值进行赋值。
Promise基本流程
Promise的api
1.promise包裹的是同步任务
<script>
let p = new Promise((resolve,reject)=>{
console.log(111);
})
console.log(222);
</script>
2.promise实例对象的catch方法,用于指定失败的回调函数。
let p = new Promise((resolve,reject)=>{
reject('error')
})
p.catch(reason => {
console.log(reason);
})
- Promise的resolve方法
快速返回一个Promise对象,分以下两种情况
<script>
let p = Promise.resolve(521) //resolve不属于Promise的实例对象,直接是Promise调用的
// 如果传入的参数为非promise对象,则返回的结果为状态成功的promise对象。
console.log(p)
</script>
let p1 = Promise.resolve(new Promise((resolve, reject) => {
reject('err')
}))
// 如果传入的参数为Promise对象,则参数的结果决定了resolve的结果,里面的promise是啥结果,这个resolve就是啥结果。
console.log(p1);
- Promise的reject方法
返回一个失败的Promise对象,不管里面传什么。传入什么,返回的Promise失败的结果就是什么
<script>
let p = Promise.reject(new Promise((resolve, reject) => {
resolve('kd')
}))
console.log(p)
</script>
- Promise函数对象的all方法
接收一个参数,一般为Promise组成的新数组,返回一个新的Promise对象,如果接收Promise数组中每个Promise对象状态都成功,它返回的Promise对象状态成功,如果数组中有一个是失败的,返回的结果就是失败的Promise对象。而且返回的Promise对象的结果是数组中每个Promise对象结果组成的数组。它失败的结果是这个Promise对象数组中失败的那个的结果。
<script>
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
// let p2 = Promise.resolve('Success');
let p2 = Promise.reject('Error');
let p3 = Promise.resolve('Oh Yeah');
//
const result = Promise.all([p1, p2, p3]);
console.log(result);
</script>
- Promise函数对象的race方法
接收一个参数,一般为Promise组成的新数组,返回一个新的Promise对象,这个新对象的结果由数组中第一个改变状态的Promise对象决定。
<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>
Promise的几个关键问题
- 代码当中如何改变Promised对象的状态,有三种方式
<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>
2.Promise对象指定多个成功或者失败的回调函数,它都会调用吗? 当Promise改变对应状态时他们都会调用。
<script>
let p = new Promise((resolve, reject) => {
// resolve('OK');
});
///指定回调 - 1
p.then(value => {
console.log(value);
});
//指定回调 - 2
p.then(value => {
alert(value);
});
</script>
- 改变Promise对象的状态与指定回调函数谁先谁后?
- 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
- 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
- promise.then()返回的新 promise 的结果状态由什么决定?
(1) 简单表达: 由 then()指定的回调函数执行的结果决定
(2) 详细表达:
- 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
- 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
- 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
<script>
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
// console.log(value);
//1. 抛出错误
// throw '出了问题';
//2. 返回结果是非 Promise 类型的对象
// return 521;
//3. 返回结果是 Promise 对象
// return new Promise((resolve, reject) => {
// // resolve('success');
// reject('error');
// });
}, reason => {
console.warn(reason);
});
console.log(result);
</script>
- promise 如何串连多个操作任务?
(1) promise 的 then()返回一个新的 promise, 可以开成 then()的链式调用
(2) 通过 then 的链式调用串连多个同步/异步任务
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
return new Promise((resolve, reject) => {
resolve("success");
});
}).then(value => {
console.log(value);
}).then(value => {
console.log(value);
})
</script>
6.Promise对象的异常穿透
(1) 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,
(2) 前面任何操作出了异常, 都会传到最后失败的回调中处理
<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);
});
//只在最后指定错误回调,如果第一个Promise状态变为失败,
//则中间的那些不管直接就指定执行错误回调的现象叫做异常穿透。
</script>
- 中断 promise 链?
(1) 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
(2) 办法: 在回调函数中返回一个 pending 状态的 promise。
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(111);
//有且只有一个方式,这个Promise对象的状态为pending,则中断。
//因为romise改变对应状态时他们才会调用
return new Promise(() => {});
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
</script>
自定义Promise(手写Promise)
class Promise {
// 构造方法
//覆盖Promise,new的时候是new我们这个
// executor(执行器函数),对应Promise接收的哪个函数参数
constructor(executor) {
// 为Promise实例对象添加属性promiseState和promiseResult,这里的this执行实例对象
this.promiseState = 'pending'
this.promiseResult = null
this.callbacks = [] //数组,为了then的回调都执行
const self = this
// 定义以下resolve函数和reject函数
// data是resolve函数和reject函数调用的形参
function resolve(data) {
// 加上一个判断,让Promise对象的状态只能更改一次
if (self.promiseState !== 'pending') return
// resolve函数一调用
// 1.改变Promise对象状态(promiseState)
self.promiseState = 'fullfilled'
// 2.改变Promise对象结果值(promiseResult)
self.promiseResult = data
// 调用成功的的回调函数,异步任务结束后在这里调
// 记得这里也让他们变成异步的
setTimeout(() => {
self.callbacks.forEach(item => {
item.onResolved(data)
})
});
}
function reject(data) {
if (self.promiseState !== 'pending') return
// 1.改变Promise对象状态(promiseState)
self.promiseState = 'rejected'
// 2.改变Promise对象结果值(promiseResult)
self.promiseResult = data
// 调用失败的的回调函数,异步任务结束后在这里调
setTimeout(() => {
self.callbacks.forEach(item => {
item.onRejected(data)
})
});
}
// throw抛出异常也要改变promise实例的状态,要想到try,catch能处理异常
// 同步调用执行器函数
// executor()这个函数,它调用的时候要传resolve和reject
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
// then方法封装
// 添加then方法
// onResolve对应then的第一个函数参数,onRejected对应then的第二个函数参数
then(onResolved, onRejected) {
const self = this
// 判断回调函数参数,第二个onRejected没传时设置默认值
if (typeof onRejected !== 'function') {
onRejected = reason => {
throw reason
}
}
if (typeof onResolved !== 'function') {
// 值传递
onResolved = value => value
// value => {return value}
}
// then方法返回的要是个Promise对象
return new Promise((resolve, reject) => {
// 封装函数
function callback(type) {
try {
let result = type(self.promiseResult)
//这里的this.promiseResult实参对应value
if (result instanceof Promise) {
// 如果是Promise类型的对象 // 这里的result是then里面的新Promise
result.then(
v => { resolve(v) },
r => { reject(r) })
} else {
// 结果的对像状态为成功
resolve(result) //这里调用的this就是then方法里面的对象
}
} catch (e) {
reject(e)
}
}
// 这里的this也指向Promise实例对象,因为我们调用时是"p.then"这样调用的
if (this.promiseState === 'fullfilled') {
// then里面的回调是异步执行的,加个定时器让它变成异步
setTimeout(() => {
callback(onResolved)
});
}
if (this.promiseState === 'rejected') {
setTimeout(() => {
callback(onRejected)
});
}
// 为了实现执行器是异步的时,then里面的回调调用
//判断pending状态
if (this.promiseState === 'pending') {
// 保存回调函数,不能直接执行then的回调,因为这时候状态还不确定
this.callbacks.push({
onResolved: function () {
callback(onResolved)
},
onRejected: function () {
callback(onRejected)
}
})
}
})
}
// catch方法封装
catch(onRejected) {
return this.then(undefined, onRejected)
}
// resolve方法封装
//添加Promise的resolve方法
// static关键字表明他是静态资源,属于类而不属于构造函数
static resolve(value) {
// 返回结果Promise对象
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
// 不是Promsie对象就将返回的那个状态设置为成功
resolve(value)
}
})
}
// reject方法封装
// 添加Promise的reject方法
static reject(reason) {
// 返回结果Promise对象
return new Promise((resolve, reject) => {
reject(reason)
})
}
// all方法封装
//添加Promise的all方法
static all(promises) {
// 返回结果为Promise对象
return new Promise((resolve, reject) => {
let count = 0
let arr = [] //存放返回结果的数组
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
// promises[i]如果成功就会调用这个回调,得知这个对象的状态是成功的
// 遍历的每个promise对象状态都成功,才能调用resolve()去修改返回的对象状态
count++
// 将当前promise对象成功的结果存入到arr中
arr[i] = v
// 判断,每次循环是成功的就给count加一个,如果count和数组长度相同就是都是成功的
if (count === promises.length) {
resolve(arr)
}
}, r => {
reject(r)
})
}
})
}
// race方法封装
static race(promises) {
// 返回结果为Promise对象
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
// 修改返回对象的状态为成功
resolve(v)
}, r => {
// 修改返回对象的状态为失败
reject(r)
})
}
})
}
}
async函数
函数的返回值为promise对象
promise对象的结果由async函数执行的返回值决定
<script>
async function main(){
// 1. 如果返回值是个非promise对象的数据,结果就是成功的状态,然后reuslt就是return后面的
// return "512"
//2. 如果返回的是个promise对象的状态,它返回promise对象受这个promise影响
// return new Promise((resolve, reject) => {
// resolve('djj')
// })
// 3. 如果抛出异常,返回的promise对象的状态就是失败的,并且result就是throw 后面的值
throw 'fjfjfjj'
}
let result = main()
console.log(result);
</script>
await表达式
await右侧的表达式一般为promsie对象,但是也可以是其他的值。
如果右侧表达式是promise对象,await返回的是promise成功的值。
如果表达式是其他值,直接将此值作为await的返回值。
注意:
await必须写在async函数中,但是async函数可以没有await.
如果await的promise失败了,就会抛出异常,需要通过try-catch捕获处理。
<script>
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>
async和await结合实践
/**
* resource 1.html 2.html 3.html 文件内容
*/
const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);
//回调函数的方式
// fs.readFile('./resource/1.html', (err, data1) => {
// if(err) throw err;
// fs.readFile('./resource/2.html', (err, data2) => {
// if(err) throw err;
// fs.readFile('./resource/3.html', (err, data3) => {
// if(err) throw err;
// console.log(data1 + data2 + data3);
// });
// });
// });
//async 与 await
async function main(){
try{
//读取第一个文件的内容
let data1 = await mineReadFile('./resource/1x.html');
let data2 = await mineReadFile('./resource/2.html');
let data3 = await mineReadFile('./resource/3.html');
console.log(data1 + data2 + data3);
}catch(e){
console.log(e.code);
}
}
main();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>async与await结合发送AJAX</title>
</head>
<body>
<button id="btn">点击获取段子</button>
<script>
//axios
function sendAJAX(url){
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open("GET", url);
xhr.send();
//处理结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判断成功
if(xhr.status >= 200 && xhr.status < 300){
//成功的结果
resolve(xhr.response);
}else{
reject(xhr.status);
}
}
}
});
}
//段子接口地址 https://api.apiopen.top/getJoke
let btn = document.querySelector('#btn');
btn.addEventListener('click',async function(){
//获取段子信息
let duanzi = await sendAJAX('https://api.apiopen.top/getJoke');
console.log(duanzi);
});
</script>
</body>
</html>