Promise 的使用
Promise 是一套处理异步场景的规范,能避免回调地狱的产生,使代码更清晰简洁。
这套规范最早诞生于前端社区,并被命名为 Promise A+ 规范。
Promise A+ 规定:
- 所有的异步场景,都可以看作是一个异步任务,每个异步任务,在 JS 中应该表现为一个对象,该对象称之为 Promise 对象,也叫做任务对象。
- 每个任务对象应该有两个阶段、三个状态:
- 未决阶段 (unsettled) :挂起状态 pending
- 已决阶段 (settled) :完成状态 fulfilled、失败状态 rejected
Promise API:new Promise(executor)
参数 executor
称为执行器,为一回调函数 (resolve, reject) => {}
:
调用 resolve
,Promise 实例的状态会从 pending 变为 fulfilled;
调用 reject
,Promise 实例的状态会从 pending 变为 rejected;
如果不调用 resolve
/ reject
,则 Promise 实例的状态将一直是 pending。
const pro1 = new Promise(() => {});
console.log('pro1', pro1); // pro1 Promise { <pending> }
const pro2 = new Promise(resolve => resolve());
console.log('pro2', pro2); // pro2 Promise { <fulfilled>: undefined }
const pro3 = new Promise((_, reject) => reject());
console.log('pro3', pro3); // pro3 Promise { <rejected>: undefined }
调用 resolve
/ reject
改变 Promise 实例状态时,可传入 1 个参数作为该 Promise 实例的值:
const pro1 = new Promise(resolve => resolve('pro1Resolve'));
console.log('pro1', pro1); // pro1 Promise { <fulfilled>: 'pro1Resolve' }
const pro2 = new Promise((_, reject) => reject('pro2Reject'));
console.log('pro2', pro2); // pro2 Promise { <rejected>: 'pro2Reject' }
除了调用 resolve / reject,抛出错误也会改变 Promise 实例的状态。抛出错误,Promise 实例的状态从 pending 变为 rejected:
const pro3 = new Promise(() => {
throw 'errorMsg';
});
console.log('pro3', pro3); // pro3 Promise { <rejected>: 'errorMsg' }
状态只能从 pending 变为 fulfilled / rejected,且状态一旦更新就不会再改变:
const pro = new Promise((resolve, reject) => {
resolve(); // resolve 写在前面,生效
throw 'error'; // 后面的 throw 无效
});
console.log('pro', pro); // pro1 Promise { <fulfilled>: undefined }
then & catch & finally
构造函数 new Promise(executor)
中的执行器 executor
是同步执行的,但 Promise 实例的 then
& catch
& finally
操作里面的回调是异步执行的,属于异步操作里面的微任务。
方法 then
& catch
& finally
都挂在 Promise.prototype 上,所以可以通过 Promise 实例 pro 直接调用。
console.log('before executor'); // before executor
new Promise(resolve => {
resolve();
console.log('executor'); // executor
}).then(() => {
console.log('then'); // then
});
console.log('after executor'); // after executor
上例输出的顺序:before executor
→ executor
→ after executor
→ then
pro.then ( onResolve, onReject )
-
回调函数
onResolve
:处理状态为 fulfilled 的 Promise 实例。 -
回调函数
onReject
:处理状态为 rejected 的 Promise 实例 (可选) 。
onReject
& onResolve
都可以接收 1 个参数,参数值为调用该方法的 Promise 实例的值:
let pro1 = Promise.resolve('Fulfilled');
pro1.then(value => {
console.log('pro1', value); // pro1 Fulfilled
});
let pro2 = Promise.reject('Rejected');
pro2.then(null, reason => {
console.log('pro2', reason); // pro2 Rejected
});
onResolve
& onReject
会返回一个新的 Promise 实例:
let pro = Promise.resolve('Fulfilled');
let proThen = pro.then(value => {
console.log('value', value); // value Fulfilled
return Promise.reject('superman');
});
console.log('proThen', proThen); // proThen Promise {<pending>}
注意:因为 then 是异步操作,所以上例的输出顺序为 proThen Promise {<pending>}
→ value Fulfilled
也因为 then 是异步操作,所以无法同步获取 proThen
;
如果想获取正确的 proThen
,可以将输出语句也写为异步操作:
const pro = Promise.resolve('pro');
const then1 = pro.then(val1 => {
console.log('val1', val1); // val1 pro
return Promise.resolve('then1Resolve');
});
setTimeout(() => {
console.log('then1', then1); // then1 Promise {<fulfilled>: 'then1Resolve'}
}, 0);
const then2 = pro.then(val2 => {
console.log('val2', val2); // val2 pro
return Promise.reject('then2Reject');
});
setTimeout(() => {
console.log('then2', then2); // then2 Promise {<rejected>: 'then2Reject'}
}, 0);
上例的输出顺序为:
val1 pro
- val2 pro
- then1 Promise {<fulfilled>: 'then1Resolve'}
- then2 Promise {<rejected>: 'then2Reject'}
如果 return
的数据不是 Promise 实例,会自动创建 fulfilled 状态的 Promise 实例,并将 return
的数据设置为 Promise 实例的值
如果没有 return
语句,则该 Promise 实例的值为 undefined
:
const pro = Promise.resolve('proFulfilled');
const then1 = pro.then(value => {
console.log('value', value); // value proFulfilled
return 'superman';
});
setTimeout(() => {
console.log('then1', then1); // then1 Promise {<rejected>: 'superman'}
}, 0);
const then2 = pro.then(value => {
console.log('value', value); // value proFulfilled
});
setTimeout(() => {
console.log('then2', then2); // then2 Promise {<fulfilled>: undefined}
}, 0);
pro.catch ( onReject )
catch(onReject)
是 then(null, onRejected)
的语法糖:
const pro = new Promise((_, reject) => reject('error'));
const proCatch = pro.catch(reason => {
console.log('reason', reason); // reason error
});
setTimeout(() => {
console.log('proCatch', proCatch); // proCatch Promise {<fulfilled>: undefined}
}, 0);
pro.finally ( callback )
- 状态为 fulfilled 和 rejected 的 Promise 实例都会被 finally 的回调函数处理
- 但是 pending 状态的 Promise 实例不会被 finally 的回调函数处理
- finally 的回调函数不接收参数
const pro1 = Promise.resolve();
pro1.finally(_ => console.log('pro1 resolve')); // pro1 resolve
const pro2 = Promise.reject();
pro2.finally(_ => console.log('pro2 reject')); // pro2 reject
const pro3 = new Promise(() => {});
pro3.finally(_ => console.log('pro3 pending'));
- finally 方法默认返回新的 Promise 实例,新的 Promise 实例的 [状态] 和 [值] 与调用 finally 的 Promise 实例一致
- 即使显式重写了新的 return 语句,该 return 语句也不会生效
const pro = Promise.resolve('pro');
console.log('pro', pro); // pro Promise {<fulfilled>: 'pro'}
const fin = pro.finally(_ => {
console.log('finally'); // finally
return Promise.resolve('finally'); // return 语句无效
});
setTimeout(() => {
console.log('fin', fin); // fin Promise {<fulfilled>: 'pro'}
console.log(fin === pro); // false
}, 0);
需要搞懂
① 改变 Promise 状态和执行回调函数,谁先执行
- 同步改变状态:先改变状态、后执行回调函数
- 异步改变状态:先执行回调函数,后改变状态
const pro1 = new Promise(resolve => resolve()); // 同步改变状态
console.log('pro1', pro1); // pro1 Promise {<fulfilled>: undefined}
const pro2 = new Promise(resolve => setTimeout(() => resolve(), 0)); // 异步改变状态
console.log('pro2', pro2); // pro2 Promise {<pending>}
② 多个回调的执行
- 如果 Promise 实例调用了多个回调函数,回调函数会按顺序执行
const pro = Promise.resolve('data');
pro.then(value => console.log(value + 1)); // data1
pro.then(value => console.log(value + 2)); // data2
③ 链式调用
- 因为
then
/catch
/finally
返回的还是一个Promise
实例,所以可以链式调用
const pro = Promise.resolve('first');
pro.then(val1 => {
console.log('val1', val1); // val1 first
return 'second';
})
.then(val2 => console.log('val2', val2)) // val2 second
.then(val3 => console.log('val3', val3)); // val3 undefined
上例中,第 1 个 then
返回了 "second"
,所以 val2
为 second
;第 2 个 then
没有返回值,所以 val3
为 undefined
const pro = Promise.reject('first');
pro.then(val1 => {
console.log('val1', val1);
return 'second';
})
.catch(reason => console.log('reason', reason)) // reason first
.then(val2 => console.log('val2', val2)); // val2 undefined
上例中,因为 pro
的状态是 rejected,所以跳过第一个 then
方法,直接到 catch
方法执行;
因为 catch
方法没有 return 语句,所以 val2
为 undefined
- 在链式调用中,没有按照规则传入回调函数的 then、catch、finally 方法,都会被直接跳过:
const p1 = Promise.resolve('p1');
p1.then(res1 => console.log('res1', res1)) // res1 p1
.then(res2 => {
console.log('res2', res2); // res2 undefined
return 'string';
})
.then()
.then('我是字符串') // 这里有两个没写回调函数的 then 方法, 会被直接跳过
.then(res3 => console.log('res3', res3)); // res3 string —— 这里 res3 接收的是前面的合法返回值
④ Promise 异常穿透
使用 Promise 的链式调用时,可以在最后设置 catch
方法,并指定失败的回调函数
这样,前面任何操作出现异常,都会传到最后的 catch
中,执行其回调函数
const pro = Promise.resolve('pro');
pro.then(val1 => {
console.log('val1', val1); // val1 pro
return Promise.reject('reject');
})
.then(val2 => {
console.log('val2', val2);
return "then2' return";
})
.catch(reason => console.log('reason', reason)); // reason reject
上例中,因为第一个 then
方法返回的是 rejected 状态的 Promise 实例,所以不会走后面的 then
方法
这样,就直接跳到了最后的 catch
方法中
const pro = Promise.resolve('ok');
pro.then(val1 => {
console.log('val1', val1); // val1 ok
throw 'error';
})
.then(val2 => console.log('val2', val2))
.catch(reason => console.log('reason', reason)); // reason error
上例中,因为第 1 个 then
方法抛出了错误,所以不会执行第二个 then
方法,直接跳到了最后的 catch
方法
⑤ 中断 Promise 链
- 中断 Promise 链,就是说在使用
then
的链式调用时,在中间断开,不再执行后面的回调函数 - 办法:在回调函数中,返回一个 pendding 状态的 Promise 对象
const pro = Promise.resolve('ok');
pro.then(val1 => {
console.log('val1', val1); // val1 ok
return new Promise(() => {});
})
.then(val2 => console.log('val2', val2))
.catch(reason => console.log('reason', reason));
上述代码中,因为第 1 个 then
返回 pendding 状态的 Promise 对象,所以 Promise 链没有继续往下执行
resolve & reject
Promise.resolve ( value )
- 用于创建 fulfilled 状态的 Promise 实例
- 参数
value
为 Promise 实例的值;不传参数的话, Promise 实例的值为undefiend
const pro1 = Promise.resolve(123);
console.log('pro1', pro1); // pro1 Promise {<fulfilled>: 123}
const pro2 = Promise.resolve();
console.log('pro2', pro2); // pro2 Promise {<fulfilled>: undefined}
- 如果参数
value
是 Promise 实例,则直接返回该实例
const pro1 = new Promise(resolve => resolve('resolve'));
const pro2 = Promise.resolve(pro1);
console.log(pro2); // Promise {<fulfilled>: 'resolve'}
console.log(pro1 === pro2); // true
Promise.reject ( reason )
- 用于创建 rejected 状态的 Promise 实例
- 参数
reason
为 Promise 实例的值;不传参的话,Promise 实例的值为undefiend
const pro1 = Promise.reject('reason');
console.log('pro1', pro1); // pro1 Promise {<rejected>: 'reason'}
const pro2 = Promise.reject();
console.log('pro2', pro2); // pro2 Promise {<rejected>: undefined}
- 与
Promise.resolve(value)
不同的是:即使参数reason
是 Promise 实例,也会成为新创建的 Promise 实例的值
const pro1 = new Promise(resolve => resolve('pro'));
const pro2 = Promise.reject(pro1);
console.log('pro2', pro2); // pro2 Promise {<rejected>: Promise}
all & race & allSettled & any
Promise.all ( promiseArr )
Promise.all(promiseArr)
接收一个 Promise 对象数组作为参数- 该方法返回一个新的 Promise 对象
当所有的 Promise 都 fulfilled 后,按照传入的 Promise 数组的顺序 将每个 Promise 对象的 resolve 值存储在一个新的数组中,并将该数组作为 Promise.all 方法返回的 Promise 对象的 resolve 值结果
const p1 = new Promise(resolve => {
setTimeout(() => {
resolve('resolve p1');
}, 500);
});
const p2 = new Promise(resolve => {
setTimeout(() => {
resolve('resolve p2');
}, 1000);
});
const p3 = new Promise(resolve => {
setTimeout(() => {
resolve('resolve p3');
}, 1500);
});
Promise.all([p1, p3, p2]).then(res => {
console.log('res', res); // res ['resolve p1', 'resolve p2', 'resolve p3'] ( 1.5s 后输出 )
});
只要有一个 Promise 被 rejected,就会立即返回一个 rejected 的 Promise 对象,其中包含第一个被 rejected 的 Promise 对象的错误信息
const p1 = new Promise(resolve => {
setTimeout(() => {
resolve('resolve p1');
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('reject p2');
}, 2000);
});
const p3 = new Promise(resolve => {
setTimeout(() => {
reject('reject p3');
}, 10000);
});
Promise.all([p1, p3, p2]).catch(err => {
console.log('err', err); // err reject p2 ( 2s 后输出 )
});
Promise.any ( promiseArr )
Promise.any(promiseArr)
接收一个 Promise 对象数组作为参数- 该方法返回一个新的 Promise 对象
如果传入的 Promise 数组中至少有一个 Promise 对象变为 resolve 状态,则 Promise.any 的返回值会变成一个 Fulfill 状态的 Promise 对象,并且这个对象的值会等于最先变为 resolve 状态的 Promise 对象的 resolve 值
const p1 = Promise.reject('reject p1');
const p2 = new Promise(resolve =>
setTimeout(() => resolve('resolve p2'), 1000)
);
const p3 = Promise.resolve('resolve p3');
Promise.any([p1, p2, p3])
.then(value => console.log(value)) // resolve p3
.catch(error => console.error(error));
如果传入的 Promise 数组中所有的 Promise 对象都变为 reject 状态,则 Promise.any 的返回值会变成一个 Reject 状态的 Promise 对象,并抛出一个 AggregateError 错误,其中包含了所有 reject 的原因信息
const p1 = Promise.reject('reject p1');
const p2 = new Promise((_, reject) =>
setTimeout(() => reject('reject p2'), 1000)
);
const p3 = Promise.reject('reject p3');
Promise.any([p1, p2, p3])
.then(value => console.log(value))
.catch(error => console.error(error)); // AggregateError: All promises were rejected
Promise.allSettled ( promiseArr )
Promise.all(promiseArr)
接收一个 Promise 对象数组作为参数- 在所有 Promise 对象都 settle 后返回一个新的 Promise 对象,该对象的 Fulfill 状态的值是一个数组,包含每个 Promise 对象的 settle 状态信息(即不管 resolve 还是 reject 都会被处理)
- 注意:若有错误抛出,则抛出错误信息,并终止函数
const p1 = new Promise(resolve => {
setTimeout(() => {
resolve('resolve p1');
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('reject p2');
}, 2000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('reject p3');
}, 3000);
});
const pas = Promise.allSettled([p1, p2, p3]).then(valArr => {
valArr.forEach((item, index) => {
console.log(`item ${index + 1}: `, item);
// item 1: {status: 'fulfilled', value: 'resolve p1'}
// item 2: {status: 'rejected', reason: 'reject p2'}
// item 3: {status: 'rejected', reason: 'reject p3'}
});
});
Promise.race ( promiseArr )
Promise.race(promiseArr)
接收一个 Promise 对象数组作为参数- 该方法返回一个新的 Promise 对象,在
promiseArr
包含的 Promise 对象中有一个对象 settled ( 已解决,即已经 fulfilled / rejected ) 时立即 resolve / reject - 注意:抛出异常的 Promise 实例不会被 Promise.race 处理
const p1 = new Promise(resolve => {
setTimeout(() => {
resolve('resolve p1');
}, 500);
});
const p2 = new Promise((_, reject) => {
setTimeout(() => {
reject('reject p2');
}, 1000);
});
const p3 = new Promise(() => {
setTimeout(() => {
throw 'error p3'; // 抛出异常, 不会被处理
}, 0);
});
Promise.race([p1, p2, p3])
.then(value => console.log('value', value)) // value resolve p1
.catch(reason => console.log('reason', reason));
可以使用 Promise.race 来实现一个超时控制,如果某个异步操作在指定时间内没有完成,就抛出超时异常:
function timeoutPromise(promise, timeout) {
// 返回一个新的 Promise 对象
return Promise.race([
promise, // 原始的 Promise 对象
new Promise((_, reject) => {
// 将给定的时间转换为毫秒单位
setTimeout(() => {
reject(new Error('Operation timed out'));
}, timeout);
}),
]);
}
const timeout = 5000; // 设置超时时间为 5 秒钟
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Operation completed successfully');
}, 3000);
});
// 调用 timeoutPromise 函数测试
timeoutPromise(promise, timeout)
.then(result => {
console.log(result); // 打印输出 "Operation completed successfully"
})
.catch(error => {
console.error(error); // 打印输出 "Error: Operation timed out"
});
上例中,setTimeout 函数模拟了一个需要等待 3 秒钟才能完成的异步操作。我们设置了超时时间为 5 秒钟,所以 Promise.race 方法会在 3 秒钟后返回原始的 Promise 对象的结果。如果在 5 秒钟内未完成该操作,则会抛出超时错误
案例
demo - fs 配合 Promise 异步读取文件:
const fs = require('fs');
// // ① 异步操作的传统解决方案:使用 [回调函数] 完成异步操作
// fs.readFile("./1.txt", (err1, data1) => {
// if (err1) return console.log(err1.message);
// console.log("data1", `${data1}`);
// fs.readFile("./2.txt", (err2, data2) => {
// if (err2) return console.log(err2.message);
// console.log("data2", `${data2}`);
// fs.readFile("./3.txt", (err3, data3) => {
// if (err3) return console.log(err3.message);
// console.log("data3", `${data3}`);
// });
// });
// });
// ② 使用 Promise 完成异步操作
new Promise((resolve, reject) => {
fs.readFile('./1.txt', (err1, data1) => {
if (err1) reject(err1.message);
resolve(data1);
});
})
.then(val1 => {
console.log('data1', `${val1}`);
// 返回异步操作的结果
return new Promise((resolve, reject) => {
fs.readFile('./2.txt', (err2, data2) => {
if (err2) reject(err2.message);
resolve(data2);
});
});
})
.then(val2 => {
console.log('data2', `${val2}`);
// 返回异步操作的结果
return new Promise((resolve, reject) => {
fs.readFile('./3.txt', (err3, data3) => {
if (err3) reject(err3.message);
resolve(data3);
});
});
})
.then(val3 => {
console.log('data3', `${val3}`);
})
.catch(err => {
console.log(err);
});
- 将 fs 封装成 Promise 对象来使用
const fs = require('fs');
function myReadFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if (err) reject(err.message);
resolve(data);
});
});
}
myReadFile('./1.txt')
.then(val1 => {
console.log('data1', `${val1}`);
// 返回异步操作的结果
return myReadFile('./2.txt');
})
.then(val2 => {
console.log('data2', `${val2}`);
// 返回异步操作的结果
return myReadFile('./3.txt');
})
.then(val3 => {
console.log('data3', `${val3}`);
})
.catch(err => {
console.log(err);
});
每次都要自己封装,略显麻烦。util.promisify
应运而生
util.promisify
的作用:将函数封装成 Promise 实例
使用 util.promisify
将 fs.readFile 方法封装成 Promise 对象
const util = require('util'); // 无需下载,直接引入即可使用
let myReadFile = util.promisify(fs.readFile);
-
也可以使用 then-fs 依赖包,then-fs 已经将 fs.readFile 方法封装好了
①
npm i then-fs
、②const thenFs = require("then-fs")
const thenFs = require('then-fs');
let myReadFile = thenFs.readFile;