js Promise
简介
Promise
是js
里一种异步编程解决方案,比较传统的是回调函数和事件,熟悉nodejs
的都知道,在Promise
还没出现之前,一切异步编程都是通过回调函数来实现的,包括文件读取,数据库操作等,很容易造成回调地狱,而Promise
可以帮我们很好的解决这个问题。
Promise
可以看做是一个容器,里面存放着未来某个时间才会结束的异步操作,其内部有三种状态,分别是pending
(进行中),fulfilled
(已成功)和rejected
(已失败)。只有内部异步操作的结果,会影响这三种状态,其他任何操作都无法影响这三种状态。
Promise
对象的状态改变,只可能有两种可能:pending -> fulfilled
或者pending -> rejected
。只要状态改变了,就不会再改变了。
基本使用
Promise
是一个构造函数,用来生成Promise
实例,Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript
引擎提供,不用自己指定。如果Promise
内部的异步操作成功,则调用resolve
函数,把Promise
内部的状态由pending
变为fulfilled
,并把结果作为参数抛出去,否则调用reject
函数把Promise
内部的状态由pending
变为rejected
,把错误作为参数抛出去。
const fs = require('fs');
// 1、创建promise对象,一经创建,立马执行
new Promise((resolve, reject) => {
fs.readFile(__dirname + '/data/a.txt', (err, data) => {
if (err) {
reject(err);
} else {
// 请求成功时,调用.then()里面自己写的resolve函数
resolve(data);
}
});
})
抛出去的结果我们可以使用Promise
的then
方法进行接收,Promise
的then
方法接收两个回调函数作为参数,第一个回调函数是Promise
对象的状态变为resolved
时调用,第二个回调函数是Promise
对象的状态变为rejected
时调用。这两个函数都是可选的,不一定要提供。它们都接受Promise
对象传出的值作为参数。
const fs = require('fs');
// 1、创建promise对象,一经创建,立马执行
new Promise((resolve, reject) => {
fs.readFile(__dirname + '/data/a.txt', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
}).then(
// 下面这个函数式作为上面Promise的resolve使用
(data) => { // 这里的参数就是上面Promise里resolve传出来的参数
console.log(data.toString());
},
// 这个作为reject使用
(err) => {
console.log(err)
}
)
使用Promise的注意点
- 调用resolve或reject并不会终结 Promise 的参数函数的执行。
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
上面代码中,调用resolve(1)
以后,后面的console.log(2)
还是会执行,并且会首先打印出来。这是因为立即resolved
的Promise
是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
一般来说一旦Promise
内部调用resolve
或者reject
之后,它的任务就完成了,不应该继续在后面执行其他任务,因此我们最好再resolve
和reject
前面加上return
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
链式调用
then
方法返回的是一个新的Promise
实例(注意,不是原来那个Promise
实例)。因此可以采用链式写法,即then
方法后面再调用另一个then
方法。
第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
const fs = require('fs');
// then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
// 第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
new Promise((resolve, reject) => {
fs.readFile('./data/a.txt', (err, data) => {
if (!err) {
resolve(data)
} else {
reject(err)
}
})
})
.then((data) => {
console.log(data.toString());
return '在.then()中返回的结果,会传入下一个then的参数中'
})
.then((data) => {
console.log(data);
return 1
})
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log('err', err);
})
输出
aaaaaaaaaaaaaaaaaaaaaa // 文件a.txt的内容
在.then()中返回的结果,会传入下一个then的参数中
1
Promise封装
假如我们要依次地区文件的内容,我们可能会像下面这样编码
const fs = require('fs');
new Promise((resolve, reject) => {
fs.readFile(__dirname + '/data/a.txt', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
}).then((data) => {
console.log(data.toString());
return new Promise((resolve, reject) => {
fs.readFile(__dirname + '/data/b.txt', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
})
}).then((data) => {
console.log(data.toString());
return new Promise((resolve, reject) => {
fs.readFile(__dirname + '/data/c.txt', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
})
}).then((data) => {
console.log(data.toString());
});
这样写没问题,但是充斥了大量的重复代码,因此我们可以对读取文件的Promise进行一层封装,改造成以下代码
const fs = require('fs');
function readFiles(...args) {
return new Promise((resolve, reject) => {
fs.readFile(...args, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
})
})
}
readFiles(__dirname + '/data/a.txt', 'utf8')
.then((data) => {
console.log(data);
return readFiles(__dirname + '/data/b.txt', 'utf8');
})
.then((data) => {
console.log(data);
return readFiles(__dirname + '/data/c.txt', 'utf8');
})
.then((data) => {
console.log(data);
return readFiles(__dirname + '/data/a.txt', 'utf8');
})
.then((data)=>{
console.log(data);
});
我们封装了一个readFiles
函数,返回一个读取文件的Promise
对象,在链式调用中,then
函数返回的也是一个Promise
对象,因此可以一层一层往下调用
错误处理
Promise
的错误处理有两种方式,一种是在then
函数的第二个回调函数参数中进行处理,另一种是使用Promise.catch
进行错误捕获。
例如:
let p = new Promise(...)
p.then(null, reject => console.log(reject))
或者
let p = new Promise(...)
p.then(resolve => console.log(resolve))
.catch(err => console.log(err))
Promise.catch
具有”冒泡”性质,意思是无论then
链式调用了多少层,Promise.catch
都能捕获到其中任意一层发生的错误
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
因此我们推荐总是使用Promise.catch
进行异常捕获
finally
不管promise
最后的状态,在执行完then
或catch
指定的回调函数以后,都会执行finally
方法指定的回调函数。
finally
方法的回调函数不接受任何参数,这意味着没有办法知道,前面的Promise
状态到底是fulfilled
还是rejected
。这表明,finally
方法里面的操作,应该是与状态无关的,不依赖于Promise
的执行结果。
const fs = require('fs');
const P1 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/a.txt', (err, data) => {
resolve(data)
})
});
const P2 = new Promise((resolve) => {
throw new Error('读取文件b.txt出错了')
});
const P3 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/c.txt', (err, data) => {
resolve(data)
})
});
Promise.all([P1, P2, P3])
.then(res => {
console.log(res);
res.map(file => console.log(file.toString()))
})
.catch(err => console.log(err))
.finally(() => console.log('文件读取完毕'))
Promise.all
Promise.all
可以把多个Promise
实例,组装成一个Promise
实例,此时只有当所有的Promise
的状态都变为fulfilled
时,Promise.all
实例的状态才会变为fulfilled
,只要其中有一个实例状态变为了rejected
,最终Promise.all
实例的状态就会变成rejected
。
Promise.all
我们常用来并发的执行异步操作,比如我们想要同时读取三个文件,使用Promise
链式调用会逐一读取,会造成一定的性能损失。
Promise.all
接收一个Promise
的数组(不一定是数组,只要有iterator
接口就行)作为参数,返回包装后的Promise
实例,实例的then
方法接收的结果是所有Promise
实例结果的数组。无论异步操作哪个先执行完毕,结果数组的顺序和Promise.all
传入的Promise
实例数组的顺序相同
再次强调一下,
Promise.all
入参不一定是数组,只要有iterator接口就行
const fs = require('fs');
const P1 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/a.txt', (err, data) => {
resolve(data)
})
});
const P2 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/b.txt', (err, data) => {
resolve(data)
})
});
const P3 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/c.txt', (err, data) => {
resolve(data)
})
});
Promise.all([P1, P2, P3]).then(res => {
console.log(res);
res.map(file => console.log(file.toString()))
})
输出
[
<Buffer 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61>,
<Buffer 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62>,
<Buffer 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63>
]
aaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccc
注意,如果作为参数的Promise
实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法。
const fs = require('fs');
const P1 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/a.txt', (err, data) => {
resolve(data)
})
});
const P2 = new Promise((resolve) => {
throw new Error('读取文件b.txt出错了')
}).catch(err => err);
const P3 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/c.txt', (err, data) => {
resolve(data)
})
});
Promise.all([P1, P2, P3])
.then(res => {
console.log(res);
res.map(file => console.log(file.toString()))
})
.catch(err => console.log(err))
结果
aaaaaaaaaaaaaaaaaaaaaa
Error: 读取文件b.txt出错了
cccccccccccccccccccccc
如果P2没有自己的catch方法,错误则会被Promise.all捕获
const fs = require('fs');
const P1 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/a.txt', (err, data) => {
resolve(data)
})
});
const P2 = new Promise((resolve) => {
throw new Error('读取文件b.txt出错了')
});
const P3 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/c.txt', (err, data) => {
resolve(data)
})
});
Promise.all([P1, P2, P3])
.then(res => {
console.log(res);
res.map(file => console.log(file.toString()))
})
.catch(err => console.log(err))
结果报错
Promise.race
Promise.race
,顾名思义,race
是竞赛的意思,Promise.race
里包装的Promise
实例,只要有一个实例的状态率先改变了,那么Promise.race
的状态就随之改变,那个率先改变状态的返回值,将作为Promise.race``then
函数的入参,无论那个实力是成功或者失败,Promise.race
的状态都会随之改变
const fs = require('fs');
const P1 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/a.txt', (err, data) => {
resolve(data)
})
});
const P2 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/b.txt', (err, data) => {
resolve(data)
})
});
const P3 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/c.txt', (err, data) => {
resolve(data)
})
});
Promise.race([P1, P2, P3]).then(res => {
console.log(res.toString())
}).catch(err => console.log(err));
Promise.race
额错误处理与Promise.all
有着相同的特性,如果作为参数的Promise
实例,自己定义了catch
方法,那么它一旦被rejected
,就不会触发Promise.race()
的catch
方法。
Promise.allSettled
只有等到所有这些参数实例都返回结果,不管是fulfilled
还是rejected
,包装实例才会结束,和前面的方法不同的是,Promise.allSettled
的返回值,不是结果数组,而是一个{status: 'fulfilled' | 'rejected', [reason|value]: VALUE}
对象的数组,如果status
是fulfilled
,表示该Promise
实例成功,那么第二个属性是value
,值是异步操作的结果,如果status
是rejected
,表示该Promise
实例有异常,那么第二个属性是reason
,值是异常信息。
const fs = require('fs');
const P1 = new Promise((resolve) => {
throw new Error('错了')
});
const P2 = new Promise((resolve) => {
throw new Error('错了')
});
const P3 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/c.txt', (err, data) => {
resolve(data)
})
});
Promise.allSettled([P1, P2, P3]).then(res => {
console.log(res)
}).catch(err => console.log(err));
结果
[
{
status: 'rejected',
reason: Error: 错了
at C:\Users\lyucan\Desktop\pro\newRepo\Repositories\myrepo\nodejs\Promise
...
},
{
status: 'rejected',
reason: Error: 错了
at C:\Users\lyucan\Desktop\pro\newRepo\Repositories\myrepo\nodejs\Promise
...
},
{
status: 'fulfilled',
value: <Buffer 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63>
}
]
往往我们需要自己手动筛选出成功的结果或失败的结果,根据需要自行处理
Promise.any
只要参数实例有任何一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是不会因为某个 Promise
变成rejected
状态而结束。
const fs = require('fs');
const Promise = require("bluebird");
const P1 = new Promise((resolve) => {
throw new Error('错了')
});
const P2 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/b.txt', (err, data) => {
resolve(data)
})
});
const P3 = new Promise((resolve) => {
fs.readFile(__dirname + '/data/c.txt', (err, data) => {
resolve(data)
})
});
Promise.any([P1, P2, P3]).then(res => {
console.log(res.toString())
}).catch(err => console.log(err));
结果
bbbbbbbbbbbbbbbbbbbbbb
如果
c.txt
文件优先读取完毕,那么结果就是c.txt
文件的内容
需要注意的是,Promise.any
还只是草案里的特性,不能直接使用,我们可以引用社区的Promise
库bluebird
来使用
Promise.resolve & Promise.reject
Promise.resolve
可以直接把一个对象转化为Promise
对象。
传入不同的对象,Promise.resolve
有不同的行为
传入
Promise
实例
如果参数是Promise
实例,那么Promise.resolve
将不做任何修改、原封不动地返回这个实例。参数是一个
thenable
对象thenable
对象指的是具有then
方法的对象,比如下面这个对象。
let thenable = {
then: (resolve, reject) => {
resolve(42);
}
};
Promise.resolve()
方法会将这个对象转为Promise
对象,然后就立即执行thenable
对象的then()
方法。
let thenable = {
then: (resolve, reject) => {
resolve(42);
}
};
let p1 = Promise.resolve(thenable); // 此时p1的状态已经变成fulfilled了
p1.then(val => console.log(val)); // 42
let thenable = {
then: (resolve, reject) => {
reject(42);
}
};
let p1 = Promise.resolve(thenable); // 此时p1的状态已经变成rejected了
p1.catch(err => console.log(err));
let thenable = {
then: (resolve, reject) => {
throw new Error('出错了')
}
};
let p1 = Promise.resolve(thenable); // 此时p1的状态已经变成rejected了
p1.catch(err => console.log(err)); // Error: 出错了
- 参数不是具有
then()
方法的对象,或根本就不是对象
let p1 = Promise.resolve('hello world');
p1.then(res => console.log(res)); // hello world
以上代码等价于
let p1 = new Promise(resolve => resolve('hello world'));
p1.then(res => console.log(res));
- 不带有任何参数
let p = Promise.resolve();
p.then(res => console.log(res)); // undefined
Promise.reject(reason)
方法也会返回一个新的Promise
实例,该实例的状态为rejected
。
let p = Promise.reject('出错了');
p.catch(err => console.log(err)); // 出错了
以上代码等同于
let p = new Promise((resolve, reject) => reject('出错了'));
p.catch(err => console.log(err)); // 出错了
最后提一下,在社区有很多库实现了Promise
,都遵循Primise/A+规范,有人测试社区的多种实现中,bluebird.js的性能比官方的ES6要高三倍,而且很多还在草案中的特性,bluebird.js
都已经实现了,如果想要使用或者学习新的Promise
特性,不妨试试bluebird.js
。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com