概述
Promise 是 JavaScript ES6 中处理异步操作的一种机制。它是一种表示异步操作最终完成或失败的对象。
异步操作的类型
- fs 文件操作
- 数据库操作
- Ajax 网络请求
- 定时器
为什么要使用 Promise
- 指定回调函数的方式更加灵活
- 支持链式调用,可以解决回调地狱问题
Promise 的特点
- 链式调用(Chaining): 可以通过
.then()
方法连接多个处理异步操作的函数,形成链式调用,使代码更清晰。 - 错误处理: 可以通过
.catch()
方法捕获 Promise 链中的任何错误,提供更方便的错误处理机制。 - 状态不可逆: 一旦进入 Fulfilled 或 Rejected 状态,就不可逆转到其他状态。
Promise 初体验
定时器
生成一个点击按钮抽奖功能,2秒后弹出中奖结果,中奖概率为30%
回调函数形式:
const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
setTimeout(() => {
const n = rand(1, 100);
if(n <= 30) {
alert('恭喜中奖');
} else {
alert('再接再厉');
}
}, 2000);
});
promise形式:
const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
const p = new Promise((resolve, reject) => {
setTimeout(() => {
const n = rand(1, 100);
if(n <= 30) {
resolve(); // 将 promise 对象的状态设置为成功
} else {
reject(); // 将 promise 对象的状态设置为失败
}
}, 2000);
// then方法传递两个参数,分别表示成功执行的方法与失败执行的方法
p.then(() => {
alert('恭喜中奖');
}, () => {
alert('再接再厉');
});
});
fs 文件操作
读取本地文件数据
回调函数形式:
const fs = require('fs');
fs.readFile('./filename.txt',(err, data) => {
if(err) throw err;
console.log(data.toString());
});
promise形式:
const p = new Promise((resolve, reject) => {
fs.readFile('./filename.txt', (err, data) => {
if(err) reject(err);
resolve(data);
});
});
p.then(value => {
console.log(value.toString());
}, reason => {
console.log(reason);
});
Ajax 网络请求
点击按钮获取网络数据
传统形式:
const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.puji.design/getData');
xhr.send();
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.response);
} else {
console.log(xhr.status);
}
}
}
});
promise形式:
const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.puji.design/getData');
xhr.send();
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
}
});
p.then(value => {
resolve(value);
}, reason => {
resolve(reason);
});
});
函数封装
function mineReadFile(path) {
return new Promise((resolve, reject) => {
require('fs').readFile(path, (err,data) => {
if(err) reject(err);
resolve(data);
});
});
}
mineReadFile('filename.txt').then(value => {
console.log(value.toString());
}, reason => {
console.log(reason);
});
Promise 对象的三种状态
- Pending(进行中): 初始状态,表示操作尚未完成,处于进行中。
- Resolved / Fulfilled(已成功): 操作成功完成,Promise 进入此状态,并返回操作的结果。
- Rejected(已失败): 操作失败,Promise 进入此状态,并返回一个包含错误信息的对象。
Promise 对象的状态只能改变一次,无论变换为成功还是失败,都会有一个结果数据。
改变 Promise 状态
- resolve() 如果当前是 pending 就会变为 resolved
- reject() 如果当前是 pending 就会变为 rejected
- 抛出异常 如果当前是 pending 就会变为 rejected
// pending => fulfilled
const p = new Promise((resolve, reject) => {
resolve('ok');
});
// pending => rejected
const p = new Promise((resolve, reject) => {
reject('error');
});
const p = new Promise((resolve, reject) => {
throw 'error';
});
Promise 对象的值
实例对象中的另一个属性 PromiseResult,存放的是异步任务成功/失败的结果。可以通过 resolve 或 reject 函数对值进行修改。
API
Promise 构造函数
Promise (excutor) {}
- excutor 函数: 执行器 (resolve, reject) => {};
- resolve 函数: 内部定义成功时调用的函数;
- reject 函数: 内部定义失败时调用的函数。
Promise.prototype.then
(onResolved, onRejected) => {}
- onResolved 函数: 成功的回调函数 value => {}
- onRejected 函数: 失败的回调函数 reason => {}
说明:指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调,返回一个新的 Promise 对象。
Promise.prototype.catch
onRejected => {}
- onRejected 函数: 失败的回调函数 reason => {}
const p = new Promise((resolve, reject) => {
reject('error');
});
p.catch(reason => {
console.log(reason);
});
方法
Promise.resolve()
返回一个成功/失败的 Promise 对象
- 如果传入的参数为非 Promise 类型的对象(如数字,字符串等),则返回的结果为成功 Promise 对象
- 如果传入的参数为 Promise 类型的对象,则参数的结果决定了 resolve 的结果
// 参数为非 Promise 类型的对象
const p1 = Promise.resolve(123);
console.log(p1);
// 返回 Promise 对象,状态 fulfilled,结果 123
// 参数为 Promise 类型的对象
const p2 = Promise.resolve(new Promise((resolve, reject) => {
reject('error');
}));
p2.catch(reason => {
console.log(reason);
});
// 返回 error
Promise.reject()
返回一个失败的 Promise 对象
Promise.all()
返回一个新的 Promise,只有所有的 Promise 都成功才成功,只要有一个失败就直接失败
Promise.race()
返回一个新的 Promise,第一个完成的 promise 的结果状态就是最终的结果状态
常见问题
改变 Promise 状态与指定回调两者的先后顺序
- 正常情况先指定回调再改变状态,但也可以先改变状态再指定回调
- 如果先改状态再指定回调
- 在执行器中直接调用 resolve()/reject()
- 延迟更长时间再调用 then()
- 什么时候得到数据
- 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
- 如果先改变状态,那当是指定回调时,回调函数就会调用,得到数据
Promise.then()返回的新 Promise 的结果状态由什么决定
- 由 then() 指定的回调函数执行的结果决定
const p = new Promise((resolve, reject) => {
resolve('success');
});
const result = p.then(value => {
...
}, reason => {
...
});
console.log(result);
// 返回值是一个 Promise 对象
如何串联多个操作任务
- Promise 的 then() 返回一个新的 Promise,可以开成 then() 的链式调用
- 通过 then 的链式调用串联起多个同步/异步任务
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
},1000);
});
p.then(value => {
return new Promise((resolve, reject) => {
resolve(2);
});
}).then(value => {
console.log(value);
});
// 返回值 2
Promise 的异常穿透
- 当使用 Promise 的 then 链式调用时,可以在最后指定失败的回调
- 前面任何操作出了异常,都会传达到最后失败的回调中处理
中断 Promise 链
当使用 Promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数
办法:在回调函数中返回一个 pendding 状态的 promise 对象
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Success');
}, 1000);
});
p.then(value=> {
console.log(111);
return new Promise(() => {});
// 后续的函数都不会执行了
}).then(value=> {
console.log(111);
}).then(value=> {
console.log(111);
}).catch(reason=> {
console.warn(reason);
})
async 与 await
async 函数
- 函数的返回值为 Promise 对象
- Promise 对象的结果由 async 函数执行的返回值决定
await 表达式
- await 右侧的表达式一般为 Promise 对象,但也可以是其他的值
- 如果表达式是 Promise 对象,await 返回的是 Promise 成功的值
- 如果表达式是其他值,直接将此值作为 await 的返回值
async function main() {
const p = new Promise((resolve, reject) => {
reject('Error');
});
try {
let res = await p;
} catch(e) {
console.log(e);
}
}