Promise构造函数
Promise是一个构造器(俗称构造函数),用来创建Promise类型的对象 。就像Array是一个构造器,用来创建数组类型对象。
格式
var p = new Promise((resolve,reject)=>{
// 某个条件下,执行resolve()
// 某个条件下,执行reject()
});
- 这个构造器的实参是一个函数,这个函数的特殊之处在于它有两个形参,这两个形参也是函数。
- 在函数体的内部, 我们做一些事情,然后根据情况来调用resolve()或者是reject()
- p是一个Promise对象。
从语法上讲,如下代码也是正确的,只是没有什么意义。
var p = new Promise( ()=> {} )
console.info(p)
typeof p // "object"
(1)我们给Promise传入了一个空函数作为实参。 (2)如果你不给Promise构造器传递任何的参数,则会报语法错误。
构造器中的代码会立即执行
var p = new Promise((resolve,reject)=>{
console.log(1);
resolve();
console.log(2)
});
以上代码中会正常输出1,2 。 调用resolve()这个方法会修改promise的状态,而不会中断函数的执行。
Promise 对象的三种状态
pending
pending,"行将发生的"。相当于是一个初始状态。 创建Promise对象时,相当于是初始状态。这个初始状态会随着你调用resolve,或者是reject函数而切换到另一种状态。
var p = new Promise((resolve,reject)=>{ console.info('发呆.....' )})
console.dir(p);
resolved
创建Promise对象时,在实参函数中调用了resolve方法。
var p = new Promise((resolve,reject)=>{ console.info('发呆.....' ); resolve();})
console.dir(p)
rejected
创建Promise对象时,调用reject方法。
var p = new Promise((resolve,reject)=>{ console.info('发呆.....' ); reject()})
console.dir(p)
三种状态的理解
-
状态是可转化
最初创建promise对象时,默认状态是pending,如果在函数体内部调用了第一个参数对应的函数,则状态变成了resolved;如果调用了第二个参数对应的函数,则状态变成了rejected。
pending ----- resolve() --> resolved; pending ----- reject() --> rejected ; -
状态转换是不可逆的。一旦从pending ---> resolved(或者是rejected),就不可能再回到pending。也不能由resolved变成rejected,或者反过来。
promise的值PromiseValue
一个promise对象除了状态之外,还有PromiseValue。在构造函数内部,这个值指的是调用resolve和reject方法时传入的实参值。
var p = new Promise( (resolve,reject) => { resolve(123); } );
// 此时,prommise对象p的状态是 resolved,值是123。
var p = new Promise( (resolve,reject) => { reject(1); } );
// 此时,prommise对象p的状态是 rejected,值是1。
单独来看PromiseValue似乎没有什么意义,它的使用场景在于结合promise对象的实例方法一起来用。
Promise实例的方法
在js中,对象会从它的构造器的原型对象中继承方法。例如:
var arr = new Array();
arr.push();
上面的push方法其实是Array.protoType中的属性。
arr.push === Array.protoType.push ; // true
同样的道理,一个promise对象也会从Promise.prototype中得方法。
如下:
- then()
- catch()
- finally()
then()方法
then方法的作用是为Promise对象添加状态改变时的回调函数。
实际上new Promise()操作本身是同步的,then/catch/finally方法才是异步执行的。
格式
它可以写两个参数,如下:
promise对象.then(参数1,参数2)
它的第二个参数是可选的,即可以只写一个参数。
promise对象.then(参数1)
它的两个参数都是函数。第一个参数是Resolved状态的回调函数,第二个参数是Reject状态的回调函数。
调用逻辑
它的两个参数都是函数。它们的执行逻辑是:
- 如果promise对象的状态是resolved,则
promisec对象.then()会自动调用第一个函数; - 状态是rejected,则
promisec对象.then()会调用第二个函数,如果此时then方法并没有设置第二个参数,就会报错;
var p = new Promise((resolve,reject)=>{
resolve();
});
p.then(()=>{console.info("then,成功")}, ()=>{console.info("then,失败")});
//------------------------------------------------------------------------------
var p = new Promise((resolve,reject)=>{
reject();
});
p.then(()=>{console.info("then,成功")}, ()=>{console.info("then,失败")});
实参的值
then的参数是两个回调函数,分别在p对象是resolve和reject自动调用。 在调用它们时,会自动把promise对象的promisevalue当作实参给它们传递进去。
示例代码1:
var p = new Promise((resolve,reject)=>{
resolve(1); //主动调用resolve,并传入
});
// 此时,P的状态是resolved,且值promiseValue 是1.
p.then((res)=>{
// 因为p的状态是resolved,所以自动执行then的第一个参数,并且把promisevalue传进来。
console.log("then,ok",res);
});
示例代码2:
var p = new Promise((resolve,reject)=>{
reject(2); //主动调用reject,并传入实参
});
// 此时,P的状态是rejected,且值promiseValue 是2.
p.then((res)=>{
// 因为p的状态是resolved,所以这句代码不会执行。
console.log("then,ok",res);
},(err)=>{
// 因为p的状态是rejected,所以自动执行then的第二个参数,并且把promisevalue传进来。
console.log("then,err",err);
});
返回值
then()方法的返回值也是一个promise对象,但是,它的返回值是一个新的promise对象(与调用then方法的并不是同一个对象),所以它支持链式写法。
那既然 p2也是一个promise对象,那么,p2的状态(promiseStatus)和值(promiseValue)分别是什么?
p2的状态及promiseValue按如下规则来确定
-
如果p1的状态是pending,则p2的状态也是pending。
-
如果p1的状态是resolved,then()会去执行f_ok,则p2的状态由f_ok的返回值决定。
- 如果f_ok返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_ok函数的return值。
- 如果f_ok返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。
-
如果p1的状态是rejected,then()会去执行f_err,则p2的状态由f_err的返回值决定。
- 如果f_err返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_err函数的return值。
- 如果f_err返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。
catch()方法
Promise.prototype.catch是.then(null, reject)的别名,用于指定发生错误时的回调函数 。比较经典的用法是这样:
new Promise((resolve,reject){
}).then(function(result){
// 如果promise对象的状态是resolved的,就会到这里来,并且result会接收promiseValue的值
}).catch(function(err){
// 如果promise对象的状态是rejected的,就会到这里来,并且err会接收promiseValue的值
});
/// 上面的代码如何拆分来写的话,等价于:
var p1 = new Promise((resolve,reject){
});
var p2 = p1.then(function(result){
});
var p3 = p2.catch(function(err){
});
finally()方法
无论状态如何,都会执行
下面我们来看一下promise.finally(f);, (1)f是一个无参函数,不论该promise最终是fulfilled还是rejected。 (2)finally不改变promise的状态。
方法异步验证
new Promise((resolve, reject) => {
console.log(1);
resolve();
})
.then(() => {
console.log('执行resolve');
})
.catch(() => {
console.log('执行reject');
})
.finally(() => {
console.log('执行finally');
});
console.log(2);
/*
执行结果:
1 2 '执行resolve' '执行finally'
可说明new Promise()本身是同步操作,then/catch/finally为异步
*/
使用promise改造回调函数
const fs = require('fs');
function readFile(filename) {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
readFile('./server.js1')
.then(rs => {
console.log(rs);
})
.catch(err => {
console.log('err', err);
});
async函数
es2017 引入async函数,让异步操作更加方便,写法上更接近同步。
async
它用来修饰一个函数,会返回一个promise对象,可以使用then方法添加回调函数。当函数执行时,如果遇到 了await就会先返回,等到异步操作完成,再接着执行函数体后面的语句。
await
- 接一个promise对象, 它会自动解析这个promise对象中的promiseValue。
- 只能出现在async函数中。
- 一般在后面接一个函数,而这个函数会返回一个promise对象
如下示例:
const fs = require('fs');
function readFile(filename) {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) {
resolve(null);
} else {
resolve(data);
}
});
});
}
async function dosomething(){
let rs = await readFile();
// if(rs)
}
应用
目的:通过promise的处理,让异步操作书写的像同步代码一样,不需要书写多层的嵌套结构
// 将读取文件功能进行封装
function readFile(path) {
// 将当前读取文件的promise对象返回
return new Promise((ok, err) => {
// 在promise中进行异步操作设置
fs.readFile(path, 'utf-8', (error, data) => {
// 如果有错误产生,触发catch
if (error) {
// 调用err()
err('读取文件错误');
} else {
// 成功时调用ok()触发then()
ok(data);
}
})
});
}
// 进行多次异步的文件读取操作
// - 当给then()设置的返回值是promise对象,这个对象会成为then的返回值
readFile('./1.json')
.then((data) => {
// 第一次文件读取
console.log(data); // 第一次输出
// 希望读取第二次文件
return readFile('./21.json')
})
.then((data) => {
console.log(data); // 第二次输出
// 希望读取第三次文件
return readFile('./3.json');
})
.then((data) => {
console.log(data); // 第三次输出
})
.catch((err) => {
console.log(err);
});
用async await 改写
ES6认为promise的.then() .catch()还是有些麻烦,又推出了一种新的写法,用来简化
// 使用async和await时,只需要设置ok的调用即可
function readFile(path) {
return new Promise((ok, err) => {
fs.readFile(path, 'utf-8', (error, data) => {
// 无论成功还是失败都触发ok
if (error) {
ok(null); // 失败时传入一些错误信息
} else {
ok(data); // 成功时传入数据
}
})
});
}
// --- 常用的写法:
// 1 后续设置一个async函数
async function myFun() {
// 2 调用可以返回promise对象的函数readFile()
// - 调用前设置await
let data1 = await readFile('./1.json');
// console.log(data1); // 可以根据data1的结果进行ifelse判断
let data2 = await readFile('./2.json');
console.log(data2);
let data3 = await readFile('./3.json');
console.log(data3);
}
myFun();
- 解决了异步调用彼此嵌套的回调地狱问题
- 解决了多个异步过程顺序执行问题
并行执行
主要是依赖Promise.all和Promise.race
Promise.all:是所有的Promise执行完毕后(reject|resolve)返回一个Promise对象。- 所有
promise对象都resolved完成,返回所有参数的resolve结果。 - 其中一个
promise对象rejected失败,此实例返回失败,失败原因是第一个失败的结果。 Promise.race是任意一个Promise对象执行完毕后返回一个Promise对象。
一旦其中一个promise对象返回成功或失败, 返回的实例就会返回成功或失败的结果。
Promise.all
Promise.all([p1, p2]).then(data=>{
console.log(data)
})
.catch(e => console.log(e);
Promise.race
// Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 打开的是 'failed'
})
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
// 1
// then 方法接受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为 then(null),这就会导致前一个 Promise 的结果会穿透下面