pomise
- promise 的介绍以及基本使用
- 使用 promise 来请求内容
- 宏任务与微任务
- promise 相关 api
promise 的介绍以及基本使用
在昨天,我们学习了使用回调函数来处理异步,但是回调函数有一个很致命的缺点,那就是回调地狱。
promise 最早就是由社区提出来的一种异步编程解决方案,主要用来解决回调地狱。
自从ES6之后,promise被纳入官方标准。
下面我们来看一下 promise简单的示例:
let pm = new Promise(function(resolve,reject){
// resovle 是在异步操作成功的时候调用
// reject 是在异步操作失败的时候调用
// 接下来,我们就可以把我们的异步操作放在 promise 里面
console.log('Hello');
resolve(); // resolve 代表异步结束后的下一步操作
console.log('World');
});
pm.then(function(){
// 相当于之前回调函数里面嵌套的下一步操作
console.log('异步结束之后要进行的下一步操作');
})
在执行 promise 的时候,异步的执行结果可以分为两种情况,一种是成功,一种是失败。成功的话会调用 resolve,失败的话会调用reject。
如果想要捕获失败的情况,可以在 then 方法里面传入第二个参数:
let pm = new Promise(function(resolve,reject){
// resovle 是在异步操作成功的时候调用
// reject 是在异步操作失败的时候调用
// 接下来,我们就可以把我们的异步操作放在 promise 里面
console.log('Hello');
reject(); // resolve 代表异步结束后的下一步操作
console.log('World');
});
pm.then(function(){
// resolve 会进入到第一个 function
console.log('成功');
},function(){
// reject 会进入到第二个 function
console.log('失败');
})
还有一种写法是通过 catch 来捕获失败的情况(推荐)
代码如下:
let pm = new Promise(function(resolve,reject){
// resovle 是在异步操作成功的时候调用
// reject 是在异步操作失败的时候调用
// 接下来,我们就可以把我们的异步操作放在 promise 里面
console.log('Hello');
reject(); // resolve 代表异步结束后的下一步操作
console.log('World');
});
pm.then(function(){
// resolve 会进入到第一个 function
console.log('成功');
}).catch(function(){
// reject 进入到 catch 方法里面
console.log('失败');
});
无论是异步操作成功还是失败,我们都可以给下一步操作传递数据,代码示例如下:
let pm = new Promise(function(resolve,reject){
// resovle 是在异步操作成功的时候调用
// reject 是在异步操作失败的时候调用
// 接下来,我们就可以把我们的异步操作放在 promise 里面
console.log('Hello');
reject("this is a test"); // resolve 代表异步结束后的下一步操作
console.log('World');
});
pm.then(function(data){
// resolve 会进入到第一个 function
// data 来自入上面 resolve 传递过来的参数
console.log(data);
}).catch(function(data){
// reject 进入到 catch 方法里面
console.log(data,'asdsada');
});
一个 promise 对象调用 then 方法之后会返回一个 promise 对象,如下:
let pm = new Promise(function(resolve,reject){
resolve("this is a test"); // resolve 代表异步结束后的下一步操作
});
pm.then(function(data){
console.log(data);
}).then(function(){
console.log('haha');
})
但是上面的代码,有一个缺点,就是后面的 then 无法通过 resolve 传递数据,所以我们可以采取手动返回一个 promise,然后就可以继续往后面传递参数了。
let pm = new Promise(function(resolve,reject){
resolve("this is a test"); // resolve 代表异步结束后的下一步操作
});
pm.then(function(data){
console.log(data);
return new Promise(function(resolve,reject){
resolve('haha')
})
}).then(function(data){
console.log(data);
})
学习完 promise 的基本语法之后,接下来我们就要使用 promise 来解决回调地狱问题:
使用回调的代码如下:
setTimeout(function(){
console.log(1);
setTimeout(function(){
console.log(2);
setTimeout(function(){
console.log(3);
},2000);
},2000);
},2000);
使用 promise来改写的代码如下:
new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(1);
resolve(2);
}, 2000)
}).then(function (data) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(data);
resolve(3);
},2000)
})
}).then(function(data){
setTimeout(function () {
console.log(data);
},2000);
})
读取文件的例子:
回调的形式,读取文件的代码如下:
const fs = require('fs'); // 引入 fs 模块
fs.readFile('./file1.txt','utf8',function(err,data){
if(err) throw err;
console.log(data);
fs.readFile('./file2.txt','utf8',function(err,data){
if(err) throw err;
console.log(data);
fs.readFile('./file3.txt','utf8',function(err,data){
if(err) throw err;
console.log(data);
});
});
});
使用 promise 读取文件内容如下:
const fs = require('fs');
const arr = [];
new Promise((resolve, reject) => {
fs.readFile('./file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
arr.push(data);
resolve();
})
}).then(() => {
return new Promise((resolve, reject) => {
fs.readFile('./file2.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
arr.push(data);
resolve();
})
})
}).then(() => {
fs.readFile('./file3.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
arr.push(data);
console.log(arr);
})
})
使用 promise 来请求内容
// 需要以服务器的形式来查看,可以使用 live server
new Promise((resolve, reject) => {
$.ajax({
url: './stu.json',
success: (data) => {
// 找出韩梅梅所在的班级id
let classId = null;
for (let i of data.student) {
if (i.name === '韩梅梅') {
classId = i.classId;
}
}
resolve(classId);
}
})
}).then((classId) => {
return new Promise((resolve, reject) => {
$.ajax({
url: './classes.json',
success: (data) => {
let teacherId = null;
for (let i of data.classes) {
if (i.id === classId) {
teacherId = i.teacherId;
}
}
resolve(teacherId);
}
});
});
}).then((teacherId) => {
$.ajax({
url: './teacher.json',
success: (data) => {
for (let i of data.teachers) {
if (i.id === teacherId) {
console.log(`韩梅梅的班主任为${i.name}`);
}
}
}
});
})
宏任务与微任务
到目前为止,我们已经知道了promise的一个基本使用方法,我们也知道了整个同步和异步任务代码执行的流程。
但是异步任务里面又要分类,分成宏任务和微任务。
常见的宏任务:
I/O 操作,setTimeout,setInterval,setImmediate,requestAnimationFrame
常见的微任务:
process.nextTick,Promise.then catch finally
执行流程:js 代码的执行顺序为,先执行主线程里面的代码,当遇到一个宏任务或者微任务就会扔给异步处理模块。
主线程的同步任务执行完成后,首先将所有的微任务全部执行一遍,接下来取出一个宏任务的执行结果来执行,再接下来又执行所有的微任务。
实战演练:
console.log(1);
setTimeout(function(){
console.log(2);
process.nextTick(function(){
console.log(3)
})
new Promise(function(resolve){
console.log(4);
resolve();
}).then(function(){
console.log(5);
})
})
process.nextTick(function(){
console.log(6);
})
new Promise(function(resolve){
console.log(7);
resolve()
}).then(function(){
console.log(8);
})
// 解析:
// 首先肯定是先输出 1,因为 console.log 是同步任务
// 接下来 setTimeout 是宏任务,会被扔到异步处理模块
// 接下来 process.nextTick 也是一个异步任务,而是是微任务
// 接下来输出 7,因为在 promise 构造里面其实是同步代码,then 后面的任务就会被放到任务队列里面(微任务)
// 整理:目前输出 1,7 宏任务队列 setTimeout 微任务队列: process.nextTick 、 then
// 接下来先执行所有的微任务:打印输出 6、8
// 在接下来,拿出一个宏任务来执行,首先打印输出 2,后面的 process.nextTick 会被放入到微任务队列,接下来的 promise,打印输出 4
// 整理:目前输出 1,7,6,8,2,4
// 最后清空微任务队列,打印输出 3,5
// 整个结果的顺序为 1,7,6,8,2,4,3,5
课堂练习:
const first = () => (new Promise((resolve, reject) => {
console.log(3);
let p = new Promise((resolve, reject) => {
console.log(7);
setTimeout(function () {
console.log('xixi');
}, 0);
setTimeout(() => {
console.log(5);
resolve(6);
}, 0);
resolve(1);
});
resolve(2);
p.then((arg) => {
console.log(arg, 'bb');
});
setTimeout(() => {
console.log('haha');
}, 0);
}));
first().then((arg) => {
console.log(arg, 'aa');
setTimeout(() => {
console.log('heihei');
}, 0);
});
setTimeout(() => {
console.log('yoyo');
}, 0);
console.log(4);
// 解析:
// 首先打印输出 3,接下来是 promise,所以打印输出 7,两个 setTime放入到宏任务队列
// resolve(1) 所对应的 then 方法放入到微任务队列,resolve(2) 放入到微任务队列,最后一个 setTimeout 放入到宏任务队列
// 整理:输出 3,7
// 宏任务:setTimeout(xixi) setTimeout(5) setTimeout(haha)
// 微任务:resolve(1) resolve(2)
// setTimeout(yoyo) 会被放入到宏任务,执行最后一句同步代码,打印输出 4
// 整理:输出 3,7,4
// 宏任务:setTimeout(xixi) setTimeout(5) setTimeout(haha) setTimeout(yoyo)
// 微任务:resolve(1) resolve(2)
// 接下来先执行 resolve(1),这里要注意要找到对应的 then 方法,打印输出 1 bb
// 接下来执行 resolve(2),打印输出 2 aa,setTimeout(heihei) 被放入到宏任务
// 整理:输出 3,7,4,1 bb,2 aa
// 宏任务 setTimeout(xixi) setTimeout(5) setTimeout(haha) setTimeout(yoyo) setTimeout(heihei)
// 微任务已经完全清空,接下来就只需要挨着执行宏任务
// 打印输出 xixi,5,haha,yoyo,heihei
// 完整的顺序:3,7,4,1 bb,2 aa,xixi,5,haha,yoyo,heihei
promise 相关 api
Promise.all
用于将多个 Promise 实例,包装成一个新的 Promise 实例。
经常用于处理多个并行的异步操作
接收一个数组,数组里面是多个 promise
示例:
let p1 = new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(1);
},1000)
})
let p2 = new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(2);
},5000)
})
let p3 = new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(3);
},200)
})
Promise.all([p1,p2,p3]).then(function(data){
console.log(data);
})
在上面的代码中,我们创建了 3 个promise,然后通过 Promise.all 来包装成一个新的 promise对象,
可以看到,3个 promise异步花费的时间是不一样的,但是 then 方法打印结果是按照数组的顺序来打印
课堂练习:使用 Promise.all 书写读取文件的示例
const fs = require('fs');
const p1 = new Promise(resolve=>{
fs.readFile('./file1.txt','utf8',(err,data)=>{
if(err) throw err;
resolve(data);
})
})
const p2 = new Promise(resolve=>{
fs.readFile('./file2.txt','utf8',(err,data)=>{
if(err) throw err;
resolve(data);
})
})
Promise.all([p1,p2]).then(function(data){
console.log(data);
})
需要注意,在多个并行的 promise 中,如果有一个 promise 的状态为 reject,那么最终的状态就为 reject
Promise.race
和上面的 all 很相似,同样是将多个 promise 实例,包装成一个 promise 实例。
但是区别在于 race 方法返回的是各个异步操作中最先结束的异步
实例如下:
let p1 = new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(1);
},1000)
})
let p2 = new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(2);
},5000)
})
let p3 = new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(3);
},200)
})
Promise.race([p1,p2,p3]).then(function(data){
console.log(data);
})
Promise.resolve
可以将现有的对象包装成 promise 对象
Promise.resolve('Hello')
等价于
new Promise(resolve=>resolve('Hello'))
示例如下:
const p = Promise.resolve('Hello');
// 等价于
// new Promise(resolve=>resolve("Hello"))
p.then(data=>{
console.log(data); // Hello
})