《JavaScript高级程序设计》读书笔记
期约
创建期约需要传入执行器函数作为参数
let p = new Promise(()=>{})
console.log(p); // Promise {<pending>}
1. 期约状态
- 待定(pending)
- 兑现(fulfilled,有时候也称为“解决”,resolved)
- 拒绝(rejected)
2. 通过执行函数控制期约状态
期约状态是私有的,所以只能在内部进行操作。通过调用它的两个函数参数实现,通常命名为reslove() 和 reject() 。状态落定后是不可逆的。
3. Promise.resolve()
可以实例化一个解决的期约。接收一个参数,可以把任何值都转化为一个期约,包括错误对象。如果本身是一个期约,它的行为类似一个空包装。因此,可以说是一个幂等方法。
let p1 = new Promise((resolve, reject) => resolve());
// 等价于
let p2 = Promise.resolve(); // Promise {<fulfilled>: undefined}
let p3 = Promise.resolve(3); // Promise {<fulfilled>: 3}
p3 === Promise.resolve(p3); // true
Promise.resolve(new Error('foo')); // Promise {<fulfilled>: Error: foo
4. Promise.reject()
如果传入一个期约对象,则这个期约会成为它返回拒绝期约的理由。
let p1 = new Promise((resolve, reject) => reject());
// 等价于
let p2 = Promise.reject(); // Promise {<rejected>: undefined}
Promise.reject(3); // Promise {<rejected>: 3}
Promise.reject(Promise.resolve()); // Promise {<rejected>: Promise}
期约的实例方法
1. Promise.prototype.then()
接收两个可选参数:onResolved 处理程序和 onRejected 处理程序。对于非函数处理程序会被静默忽略。如果没有显示的返回语句,则原样往后传,默认的返回值 undefined。抛出异常会返回拒绝的期约。
let p =Promise.resolve('foo');
p.then(() => console.log('res'), () => console.log('rej')); // res
p.then(() => {throw 'bar'}, () => console.log('rej')); // Promise {<rejected>: 'bar'}
2. Promise.prototype.catch()
一个语法糖,用于给期约添加错误处理程序。只接收一个参数:onRejected 处理程序。相当于调用 Promise.prototype.then(null, onRejected);
3. Promise.prototype.finally()
用于给期约添加 onFinally 处理程序,这个程序在期约转换为解决或拒绝状态时都会执行。
4. 非重入期约方法
当期约进入落地状态时,与该状态相关的处理程序仅仅会被排期,而非立即执行。跟着这个程序后面的同步代码一定会在该程序之前先执行。即使期约一开始就是与附加处理程序关联的状态。
5. 邻近处理程序的执行顺序
如果给期约添加了多个处理程序,当期约状态变化时,相关处理程序会被按照添加它们的顺序依次执行。
let p1 = Promise.resolve();
p1.then(() => setTimeout(console.log, 0, 1));
p1.then(() => setTimeout(console.log, 0, 2));
// 1
// 2
静态方法
promise.all()
会在一组期约全部解决之后再解决。接收一个可迭代对象,返回一个新的期约。
返回值:
- 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的Promise Promise。
- 如果传入的参数不包含任何
promise
,则返回一个异步完成(asynchronously resolved) 的Promise。 - 其它情况下返回一个处理中(pending)的Promise。这个返回的
promise
之后会在所有的promise
都完成或有一个promise
失败时异步地变为完成或失败。
promise.race()
是一组集合中最先解决或拒绝的期约的镜像。接收一个可迭代对象,返回一个新的期约。只要是第一个落地的期约,就会包装其解决值或拒绝理由并返回新的期约。
串行期约合成
基于后续期约使用之前期约的返回值来串联期约。
function compose(...fns) {
return (x) => fns.reduce((promise, fn) => promise.then(fn), Promise.resolve(x));
}
期约扩展
1. 期约取消
<body>
<button id="start">Start</button>
<button id="cancel">Cancel</button>
</body>
<script>
class CancelToken {
constructor(cancelFn){
this.promise = new Promise((resolve, reject) => {
cancelFn(() => {
setTimeout(console.log, 0 ,"delayed cancelled");
resolve();
})
})
}
}
const startButton = document.getElementById('start');
const cancelButton = document.getElementById('cancel');
function cancellableDelayedResolve(delay){
setTimeout(console.log, 0, "set delay");
return new Promise((resolve, reject) => {
const id = setTimeout((() => {
setTimeout(console.log, 0, "delayed resolve")
resolve();
}), delay);
const cancelToken = new CancelToken((cancelCallback) =>
cancelButton.addEventListener("click", cancelCallback)
)
cancelToken.promise.then(() => clearTimeout(id));
});
}
startButton.addEventListener("click", () => cancellableDelayedResolve(1000));
</script>
2. 期约进度通知
class TrackablePromise extends Promise {
constructor(executor) {
const notifyHandlers = [];
super((resolve, reject) => {
return executor(resolve, reject, (status) => {
notifyHandlers.map((handler) => handler(status));
});
});
this.notifyHandlers = notifyHandlers;
}
notify(notifyHandler) {
this.notifyHandlers.push(notifyHandler);
return this;
}
}
let p = new TrackablePromise((resolve, reject, notify) => {
function countdown(x) {
if(x > 0){
notify(`${20 * x }% remaining`);
setTimeout(() => countdown(x -1), 1000);
} else {
resolve();
}
}
countdown(5);
});
p.notify((x) => setTimeout(console.log, 0, 'progress:', x));
p.then(() => setTimeout(console.log, 0, 'completed'));
// (约1秒后) progress: 80% remaining
// (约2秒后) progress: 60% remaining
// (约3秒后) progress: 40% remaining
// (约4秒后) progress: 20% remaining
// (约5秒后) completed
异步函数
1. async
用于声明异步函数。可以用在函数声明、函数表达式、箭头函数和方法上。异步函数如果使用了return 关键字返回值(如果没有return 则返回undefined),该值会被 Promise.resolve() 包装成一个期约对象。
异步函数的返回值期待一个实现 thenable 接口的对象,常规的值也可以。如果返回的是实现 thenable 接口的对象,则可以由then() “解包”。如果不是,则返回值就被当作已经解决的期约。
async function foo() {
return 'foo'
}
foo().then(console.log); // foo
async function bar() {
return ['bar']
}
bar().then(console.log); // ['bar']
async function baz() {
const thenable = {
then(callback) { callback('baz'); }
};
return thenable;
}
baz().then(console.log); // baz
2. await
暂停异步函数代码的执行,等待期约解决。async/await 中真正起作用的是 await。异步函数如果不包含await 关键字,其执行基本上跟普通函数一样。
async function foo() {
console.log(await 'foo');
}
foo(); // foo
3. await 的限制
必须在异步函数中使用,不能再顶级上下文如<script> 标签或模块中使用。
异步函数策略
1. 实现sleep()
async function sleep(delay) {
return new Promise((resolve) => setTimeout(resolve, delay));
}
async function foo() {
const t0 = Date.now();
await sleep(1500);
console.log(Date.now() - t0);
}
foo(); // 1514
2. 利用平行执行
async function randomDelay(id) {
const delay = Math.random() * 1000;
return new Promise((resolve) => setTimeout(() => {
console.log(`${id} finished`);
resolve(id);
}, delay));
}
async function foo() {
const t0 = Date.now();
const promises = Array(5).fill(null).map((_, i) => randomDelay(i));
for(const p of promises) {
console.log(`awaited ${await p}`);
}
console.log(`${Date.now() - t0} ms elapsed`);
}
foo();
// 2 finished
// 1 finished
// 4 finished
// 3 finished
// 0 finished
// awaited 0
// awaited 1
// awaited 2
// awaited 3
// awaited 4
// 945 ms elapsed
3. 串行执行期约
async function addTwo(x) {
return x + 2;
}
async function addThree(x) {
return x + 3;
}
async function addFive(x) {
return x + 5;
}
async function addTen(x) {
for( const fn of [addTwo, addThree, addFive]) {
x = await fn(x);
}
return x;
}
addTen(9).then(console.log); // 19