前言
熟悉 js 的小伙伴们应该都知道什么叫做回调地狱,当我们需要在一个异步请求完成之后再进行一系列的操作,就很有可能出现下面这种场景:
let fs = require("fs");
fs.readFile("1.txt","utf-8",function(err,data){ // 读取文件1.txt
console.log(data); // 2.txt
fs.readFile(data,"utf-8",function(err,data){ // 读取文件2.txt
console.log(data); // 3.txt
fs.readFile(data,"utf-8",function(err,data){ // 读取文件3.txt
console.log(data); // 一层一层又一层
})
})
})
所以,为了解决这个问题,Promise 应运而生,它解决了回调地狱的问题,维护简单,then可以按照顺序执行:
Promise 基本使用
Promise 是个什么东西呢?这是 ES6 中提出的一个步编程的解决方案,比传统的异步解决方案【回调函数】和【事件】更合理、更强大。
这是它的常用写法:
new Promise(请求1)
.then(请求2(请求结果1))
.then(请求3(请求结果2))
... // 这里可以跟很多个 then
.catch(处理异常(异常信息))
// 我们在实际应用中一般这么写
let p1 = new Promise((resolve, reject) => {
if (success) {
resolve(res); // 本次异步函数的状态变为 resolve
} else {
reject(res); // 本次异步函数的状态变为 reject
}
});
// promise 的主题写好了,那么我们来写调用
p1.then(res => {
// 成功的回调
}).catch(err=>{
// 失败的回调
});
下面是实际应用~~
let fs = require("fs");
// 封装了一个read方法返回一个Promise的实例
function read(filePath,encoding){
return new Promise((resolve,reject)=>{
fs.readFile(filePath,encoding,(err,data)=>{
if(err) reject(err);
resolve(data);
})
})
}
fs.read("1.txt","utf-8").then(data=>{
fs.read(data,"utf-8"); // data相当于于callback的成功回调
}).then(data=>{
fs.read(data,"utf-8");
});
Promise 的状态
我们来说说 Promise 的 3 个状态~~
| 状态 | 说明 |
|---|---|
| Pending | 等待中...这是创建 Promise 对象时的初始状态 |
| Resolved | 成功 |
| Rejected | 失败 |
在 pending 阶段,如果发生未捕获的错误或者主动调用 reject,都会将状态推向至 rejected,并被 catchable 捕获
每当我们改变了 promise 的状态,那么之后就不能再更改了,所以说 promise 状态发生改变的时机其实是在 pending 的时候才会执行。
那么问题来了,then、catch、finally 返回 promise 但这些 promise 的状态由谁决定呢?
答案是如果处理了状态那新得到的 promise 的状态由处理函数的具体内容决定,如果没处理状态那得到 promise 的状态直接继承前面 promise 的状态。
Promise.prototype.finally()
这个 api 一般放在链式调用的结尾,意味着,不论这个 promise 的最终状态是什么,都会执行 finally 里的方法
so~ 我们就可以得到一个完整的状态处理流程
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(100);
}, 2000)}).then(result => {
return result * num // 这里模拟一个错误,num 未定义
}).catch(err => {
console.log(err); // num is not defined
}).finally(() => {
console.log('complete'); // complete
})
// 可以简写成下面这样
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('err'))
}, 2000)
});
p1.then(res => {
// 成功的回调
}).catch(err=>{
// 失败的回调
}).finally(()=>{
// 最后的执行
});
Promise.prototype.all()
但是这里会有一个什么样的问题呢?当我们需要同时执行多个函数之后再去进行异步操作时,如何解决?
比如,我需要让 f1 函数执行 2 次之后再执行 f3
这里 Promise 也给我们提供了方法,那就是 Promise.all
语法:
Promise.all([A, B]).then((resA, resB) => {
... // do something
}).catch(() => {
... // do something
})
🌰如下~~
let fs = require("fs");
// 封装了一个read方法返回一个Promise的实例
function read(filePath,encoding){
return new Promise((resolve,reject)=>{
fs.readFile(filePath,encoding,(err,data)=>{
if(err) reject(err);
resolve(data);
})
})
}
// all执行后会返回一个新的promise,所以promise执行成功后才算成功,返回值是数组类型,有一个失败就失败了(&&)
Promise.all([read("1.txt","utf-8"),read("2.txt","utf-8")]).then(data=>{
console.log(data);
})
如上的例子所示,使用 Promise.all 方法就轻松的解决了上面的问题~~
但是如果不用 Promise.all,只用基础的 Promise ,如何解决这种问题呢?
我们可以这样写:
promsiseA.then(() => {
return promsiseB;
}).then(() => {
... // do something
}).catch(() => {
// do something
})
而 Promise.all 也有另外一种写法,那就是 Promise.race, 用法和 Promise.all 一毛一样~~
实现一个简单的 Promise
在实现一个 Promise 之前呢,我们要知道一个 Promise 的注意点有哪些~~
- Promise 有 3 个状态:pending / fulfilled / rejected
- 构建一个 Promise 实例需要给 Promise 的构造函数传入一个函数,传入的函数有两个 function 类型的形参,分别是 resolve 和 reject
- Promise 上还有 then 方法,then 方法就是用来指定 Promise 对象的状态改变时确定执行的操作,resolve 时执行第一个函数(onFulfilled),reject 时执行第二个函数(onRejected)
- 当状态变为 resolve 时便不能再变为 reject,反之同理。
所以,根据以上几个要点我们可以写一个简单的 promise:
function Promise (executor) {
let _this = this;
_this.status = 'pending';
_this.value = undefined; // 成功的标志
_this.reason = undefined; // 失败的标志
function resolve (value) {
if (_this.status === 'pending') {
_this.status = 'resolved';
_this.value = value;
}
}
function reject (reason) {
if (_this.status === 'pending') {
_this.status = 'rejected';
_this.reason = reason;
}
}
executor(resolve, reject);
}
Promise.prototype.then = function (onFulfilled, onRejected) {
let _this = this;
if (_this.status === 'resolved') {
onFulfilled(_this.value);
}
if (_this.status === 'rejected') {
onFulfilled(_this.reason);
}
}
module.exports = Promise;
总结
学完之后最好自己巩固一下啦,刷点题练习一下~~ 练习