解决问题:回调地狱(callback hell)
引用很经典的一张图,可以看出回调地狱
带来的不友好的书写方式
例:使用Node按顺序异步读取文件(与浏览器中运行使用异步方法同理)
const fs = require('fs');
fs.readFile('./static/a.txt','utf8',function(err,data){
if(err){
//抛出异常
//作用:阻止程序执行,并把错误信息打印到控制台
throw err;
}
console.log(data);
});
fs.readFile('./static/b.txt','utf8',function(err,data){
if(err){
throw err;
}
console.log(data);
});
fs.readFile('./static/c.txt','utf8',function(err,data){
if(err){
throw err;
}
console.log(data);
});
上述代码中,可以看出同时执行了三个异步方法,按照原本的需求,需要按顺序读取三个文件
a.txt->b.txt->c.txt
,虽然代码中我们是按照先后的顺序书写的,但是执行的顺序不一定
是按照书写的顺序,由于是异步方法,不需要等待,打印的顺序取决于读取的文件大小
控制台打印结果
可以看出,多次打印,确实会出现打印顺序不一致
的情况
E:\笔记\js>node promise.js
hello a
hello b
hello c
E:\笔记\js>node promise.js
hello a
hello c
hello b
E:\笔记\js>node promise.js
hello a
hello b
hello c
为了保证文件的读取顺序,可以使用回调嵌套
的方式
fs.readFile('./static/a.txt', 'utf8', function (err, data) {
if (err) {
throw err;
}
console.log(data);
fs.readFile('./static/b.txt', 'utf8', function (err, data) {
if (err) {
throw err;
}
console.log(data);
fs.readFile('./static/c.txt', 'utf8', function (err, data) {
if (err) {
throw err;
}
console.log(data);
});
});
});
上述的代码虽然解决了读取顺序的问题,但是同时也产生了文章开头所说的回调地狱
的问题,这就是我们为什么要使用Promise?
Promise介绍
Promise是ES6新增的API
Promise的英文就是承诺,保证的意思
三种状态:1.Pending
(默认状态)2.Resolved
(成功)3.Rejected
(失败)
Promise相当于一个容器,本身不是异步,里面的任务是异步
Promise基础语法
//创建Promise容器(构造函数)
var p1 = new Promise(function(resolve,reject){
fs.readFile('./static/a.txt','utf8',function(err,data){
if(err){
// 失败,容器中的任务失败
// console.log(err);
reject(err);//把容器的Panding状态变为Rejected,实际上就是then方法传递的方法2
}
//容器中的任务成功
// console.log(data);
resolve(data);//把容器的Pending改为Resolved,实际上就是then方法传递的方法1
});
});
//当p1成功了就是then(英文意思:然后)
p1.then(function(data){
console.log(data);
},function(err){
console.log(err);
})
then方法链式调用读取文件
//创建Promise容器(构造函数)
var p1 = new Promise(function(resolve,reject){
fs.readFile('./static/a.txt','utf8',function(err,data){
if(err){
// 失败,容器中的任务失败
// console.log(err);
reject(err);//把容器的Panding状态变为Rejected,实际上就是then方法传递的方法2
}
//容器中的任务成功
// console.log(data);
resolve(data);//把容器的Pending改为Resolved,实际上就是then方法传递的方法1
});
});
var p2 = new Promise(function(resolve,reject){
fs.readFile('./static/b.txt','utf8',function(err,data){
if(err){
// 失败,容器中的任务失败
// console.log(err);
reject(err);//把容器的Panding状态变为Rejected,实际上就是then方法传递的方法2
}
//容器中的任务成功
// console.log(data);
resolve(data);//把容器的Pending改为Resolved,实际上就是then方法传递的方法1
});
});
var p3 = new Promise(function(resolve,reject){
fs.readFile('./static/c.txt','utf8',function(err,data){
if(err){
// 失败,容器中的任务失败
// console.log(err);
reject(err);//把容器的Panding状态变为Rejected,实际上就是then方法传递的方法2
}
//容器中的任务成功
// console.log(data);
resolve(data);//把容器的Pending改为Resolved,实际上就是then方法传递的方法1
});
});
//当p1成功了就是then(英文意思:然后)
p1.then(function(data){
//p1读取成功时候
console.log(data);
// 当前函数中return的结果就可以在后面的then的function中接收
return p2;//返回p2这个Promise对象
},function(err){
console.log(err);
})
.then(function(data){
console.log(data);//打印p2 resolve返回的值
return p3;//返回p3这个Promise对象
})
.then(function(data){
console.log(data);//打印p3 resolve返回的值
});
上述代码虽然长,但是代码结构清晰(then中参数二可不传
)
简化代码,我们可以使用Promise封装读取文件的方法
// 封装读取文件的方法
function PromiseReadFile(filePath){
// 返回一个Promise对象
return new Promise(function(resolve,reject){
fs.readFile(filePath,'utf8',function(err,data){
if(err){
// 失败,容器中的任务失败
// console.log(err);
reject(err);//把容器的Panding状态变为Rejected,实际上就是then方法传递的方法2
}
//容器中的任务成功
// console.log(data);
resolve(data);//把容器的Pending改为Resolved,实际上就是then方法传递的方法1
});
});
}
// 链式调用
PromiseReadFile('./static/a.txt')
.then(function(data){
console.log(data);
return PromiseReadFile('./static/b.txt');
})
.then(function(data){
console.log(data);
return PromiseReadFile('./static/c.txt');
})
.then(function(data){
console.log(data);
});
以上就是Promie的基本使用,通过封装可以看出代码结构更加简洁明了,从而避免了回调地狱带来的问题,使代码看上去更加优雅