Promise 深入 + 自定义 Promise
前置知识准备
函数对象与实例对象
函数对象:将函数作为对象使用时, 简称为函数对象。
实例对象:new 函数产生的对象, 简称为实例对象。
function Fn() {
// Fn函数
}
const fn = new Fn(); // Fn是构造函数 fn是实例对象(简称为对象)
console.log(Fn.prototype); // Fn是函数对象
Fn.call({}); // Fn是函数对象
$('#test'); // jQuery函数
$.get('/test'); // jQuery函数对象
两种类型的回调函数
同步回调
理解:立即执行, 完全执行完了才结束, 不会放入回调队列中
例子:数组遍历相关的回调函数 / Promise 的 excutor 函数
const arr = [1, 3, 5];
arr.forEach((item) => {
// 遍历回调, 同步回调函数, 不会放入列队, 一上来就要执行完
console.log(item);
});
console.log('forEach()之后');
// 1
// 3
// 5
// forEach()之后
异步回调
理解:不会立即执行, 会放入回调队列中将来执行
例子:定时器回调 / ajax 回调 / Promise 的成功|失败的回调
setTimeout(() => {
// 异步回调函数, 会放入队列中将来执行
console.log('timout callback()');
}, 0);
console.log('setTimeout()之后');
// setTimeout()之后
// timout callback()
程序错误
错误类型
-
Error: 所有错误的父类型
-
ReferenceError: 引用的变量不存在
-
TypeError: 数据类型不正确的错误
-
RangeError: 数据值不在其所允许的范围内
-
SyntaxError: 语法错误
/* ReferenceError: 引用的变量不存在 */
console.log(a)
// ReferenceError: a is not defined
console.log('-----') // 没有捕获error, 下面的代码不会执行
/* TypeError: 数据类型不正确的错误 */
let b
console.log(b.xxx)
// TypeError: Cannot read property 'xxx' of undefined
b = {}
b.xxx()
// TypeError: b.xxx is not a function
/* RangeError: 数据值不在其所允许的范围内 */
function fn() {
fn()
}
fn()
// RangeError: Maximum call stack size exceeded
/* SyntaxError: 语法错误 */
const c = """"
// SyntaxError: Unexpected string
错误处理
捕获错误:try … catch
try {
let d;
console.log(d.xxx);
} catch (error) {
console.log(error.message);
console.log(error.stack);
}
console.log('出错之后');
// Cannot read property 'xxx' of undefined
// TypeError: Cannot read property 'xxx' of undefined
// at <anonymous>:3:19
// 出错之后
抛出错误:throw error
function something() {
if (Date.now() % 2 === 1) {
console.log('当前时间为奇数, 可以执行任务');
} else {
// 如果时间是偶数抛出异常, 由调用来处理
throw new Error('当前时间为偶数无法执行任务');
}
}
// 捕获处理异常
try {
something();
} catch (error) {
alert(error.message);
}
message 属性: 错误相关信息
stack 属性: 函数调用栈记录信息
Promise 深入
含义
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise
对象。
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
特点
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
基本使用
// 1. 创建一个新的promise对象
const p = new Promise((resolve, reject) => {
// 执行器函数 同步回调
console.log('执行 excutor');
// 2. 执行异步操作任务
setTimeout(() => {
const time = Date.now(); // 如果当前时间是偶数就代表成功, 否则代表失败
// 3.1. 如果成功了, 调用resolve(value)
if (time % 2 == 0) {
resolve('成功的数据, time=' + time);
} else {
// 3.2. 如果失败了, 调用reject(reason)
reject('失败的数据, time=' + time);
}
}, 1000);
});
console.log('new Promise()之后');
p.then(
(value) => {
// 接收得到成功的value数据 onResolved
console.log('成功的回调', value);
},
(reason) => {
// 接收得到失败的reason数据 onRejected
console.log('失败的回调', reason);
}
);
// 执行 excutor
// new Promise()之后
// 失败的回调 失败的数据, time=1615801107751
为什么要用 Promise
-
指定回调函数的方式更加灵活:
旧的:必须在启动异步任务前指定
promise:启动异步任务 => 返回 promie 对象 => 给 promise 对象绑定回调函数(甚至可以在异步任务结束后指定)
-
支持链式调用, 可以解决回调地狱问题
什么是回调地狱? 回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调函数执行的条件
回调地狱的缺点? 不便于阅读 / 不便于异常处理
解决方案? promise 链式调用
终极解决方案? async/await
Promise 的 API
-
Promise 构造函数: Promise (excutor) {}
excutor 函数: 同步执行 (resolve, reject) => {}
resolve 函数: 内部定义成功时我们调用的函数 value => {}
reject 函数: 内部定义失败时我们调用的函数 error => {}
说明: excutor 会在 Promise 内部立即同步回调,异步操作在执行器中执行
-
Promise.prototype.then 方法: (onResolved, onRejected) => {}
onResolved 函数: 成功的回调函数 (value) => {}
onRejected 函数: 失败的回调函数 (error) => {}
说明: 指定用于得到成功 value 的成功回调和用于得到失败 error 的失败回调
返回一个新的 promise 对象
-
Promise.prototype.catch 方法: (onRejected) => {}
onRejected 函数: 失败的回调函数 (error) => {}
说明: then()的语法糖, 相当于: then(undefined, onRejected)
-
Promise.resolve 方法: (value) => {}
value: 成功的数据或 promise 对象
说明: 返回一个成功/失败的 promise 对象
-
Promise.reject 方法: (error) => {}
error: 错误内容
说明: 返回一个失败的 promise 对象
-
Promise.all 方法: (promises) => {}
promises: 包含 n 个 promise 的数组
说明: 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就直接失败
-
Promise.race 方法: (promises) => {}
promises: 包含 n 个 promise 的数组
说明: 返回一个新的 promise, 第一个完成的 promise 的结果状态就是最终的结果状态
Promise 的几个关键问题
-
如何改变 promise 的状态?
(1)resolve(value): 如果当前是 pendding 就会变为 resolved
(2)reject(error): 如果当前是 pendding 就会变为 rejected
(3)抛出异常: 如果当前是 pendding 就会变为 rejected
const p = new Promise((resolve, reject) => { resolve(1); // promise变为resolved成功状态 reject(2); // promise变为rejected失败状态 throw new Error('出错了'); // 抛出异常, promse变为rejected失败状态, error为抛出的error throw 3; // 抛出异常, promse变为rejected失败状态, error为 抛出的3 });
-
一个 promise 指定多个成功/失败回调函数, 都会调用吗?
当 promise 改变为对应状态时都会调用
-
改变 promise 状态和指定回调函数谁先谁后?
(1)都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
(2)如何先改状态再指定回调?
① 在执行器中直接调用 resolve()/reject()
② 延迟更长时间才调用 then()
(3)什么时候才能得到数据?
① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
② 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
-
promise.then()返回的新 promise 的结果状态由什么决定?
(1)简单表达: 由 then()指定的回调函数执行的结果决定
(2)详细表达:
① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
② 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
③ 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
-
promise 如何串连多个操作任务?
(1)promise 的 then()返回一个新的 promise, 可以写成 then()的链式调用
(2)通过 then 的链式调用串连多个同步/异步任务
-
promise 异常传/穿透?
(1)当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调
(2)前面任何操作出了异常, 都会传到最后失败的回调中处理
// Promise.prototype.then()的默认调用 Promise.prototype.then( (value) => Promise.resolve(calue), (error) => Promise.reject(error) );
-
中断 promise 链?
(1)当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
(2)办法: 在回调函数中返回一个 pendding 状态的 promise 对象
return new Promise(() => {});
自定义(手写)Promise
代码
/**
* 自定义Promise类
*/
class Promise {
/**
* Promise 构造函数
* executor:执行器函数(同步执行)
*/
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.callbacks = [];
const resolve = (value) => {
if (this.status !== PENDING) {
return;
}
this.status = RESOLVED;
this.data = value;
if (this.callbacks.length > 0) {
setTimeout(() => {
// 异步调用已在队列中的回调函数
this.callbacks.forEach((callback) => {
callback.onResolved(value);
});
});
}
};
const reject = (error) => {
if (this.status !== PENDING) {
return;
}
this.status = REJECTED;
this.data = error;
if (this.callbacks.length > 0) {
setTimeout(() => {
// 异步调用已在队列中的回调函数
this.callbacks.forEach((callback) => {
callback.onRejected(error);
});
});
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
/**
* Promise 原型对象的 then()
* 指定成功和失败的回调函数
* 返回一个新的 Promise 对象
*/
then(onResolved, onRejected) {
onResolved = typeof onResolved === 'function' ? onResolved : (value) => value;
onRejected =
typeof onRejected === 'function'
? onRejected
: (error) => {
throw error;
};
return new Promise((resolve, reject) => {
const handle = (callback) => {
try {
const result = callback(this.data);
if (result instanceof Promise) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (error) {
reject(error);
}
};
if (this.status === RESOLVED) {
setTimeout(() => {
handle(onResolved);
});
} else if (this.status === REJECTED) {
setTimeout(() => {
handle(onRejected);
});
} else {
this.callbacks.push({
onResolved: () => {
handle(onResolved);
},
onRejected: () => {
handle(onRejected);
},
});
}
});
}
/*
* Promise 原型对象的 catch()
* 指定失败的回调函数
* 返回一个新的 Promise 对象
*/
catch(onRejected) {
return this.then(undefined, onRejected);
}
/**
* Promise 函数的对象的 resolve 方法
* 返回一个指定 error 的失败的 promise
*/
static resolve(value) {
if (value instanceof Promise) {
return value;
} else {
return new Promise((resolve, reject) => {
resolve(value);
});
}
}
/**
* Promise 函数的对象的 reject 方法
* 返回一个指定 error 的失败的 promise
*/
static reject(error) {
return new Promise((resolve, reject) => {
reject(error);
});
}
/**
* Promise 函数对象的 all 方法
* 返回一个 promise,只有当所有的 promise 都成功时才成功,否则只要有一个失败就失败
*/
static all(promises) {
return new Promise((resolve, reject) => {
let values = [];
promises.forEach((promise, index) => {
promise = Promise.resolve(promise);
promise.then(
(value) => {
values[index] = value;
if (values.length === promises.length) {
resolve(values);
}
},
(error) => {
reject(error);
}
);
});
});
}
/**
* Promise 函数对象的 race 方法
* 返回一个 promise,其结果由第一个完成的 promise 决定
*/
static race(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise) => {
promise = Promise.resolve(promise);
promise.then(resolve, reject);
});
});
}
}
遇到的问题
-
this 的指向
在 executor 中,因为设定了 setTimeout,因而 resolve 调用时的 this 指向为当前运行环境的 this(window),因此通过箭头函数,将 this 设置为函数创建时的 this 即 promise 实例可以解决 resolve 调用时 this 指向问题。
在 then 方法中,handle 实际调用时 this 同样为 window,通过箭头函数解决 this 指向问题。
-
微任务和宏任务
在 es6 中 then 中的函数会放入微任务中,而因为开发者无法向微任务队列中添加任务,所以用宏任务替代,即用 setTimeout 实现异步执行,详细问题见 JavaScript 中的异步编程
-
then 方法如何书写?
- 返回一个 Promise 实例,其中存储了 then 中代码运行状态的信息;
- 运行状态信息受到 onResolved 和 onRejected 的执行结果影响;
- then 同步执行,then 中的函数异步执行且受调用 then 的 promise 实例状态的控制;
- 异常穿透;
面试题
面试题 1
setTimeout(() => {
console.log(1);
}, 0);
Promise.resolve().then(() => {
console.log(2);
});
Promise.resolve().then(() => {
console.log(4);
});
console.log(3);
// 3 2 4 1
面试题 2
setTimeout(() => {
console.log(1);
}, 0);
new Promise((resolve) => {
console.log(2);
resolve();
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
});
console.log(5);
// 2 5 3 4 1
面试题 3
const first = () =>
new Promise((resolve, reject) => {
console.log(3);
let p = new Promise((resolve, reject) => {
console.log(7);
setTimeout(() => {
console.log(5);
resolve(6);
}, 0);
resolve(1);
});
resolve(2);
p.then((arg) => {
console.log(arg);
});
});
first().then((arg) => {
console.log(arg);
});
console.log(4);
// 3 7 4 1 2 5
注意:promise 状态一但变化就固定下来了。
面试题 4
setTimeout(() => {
console.log('0');
}, 0);
new Promise((resolve, reject) => {
console.log('1');
resolve();
})
.then(() => {
console.log('2');
new Promise((resolve, reject) => {
console.log('3');
resolve();
})
.then(() => {
console.log('4');
})
.then(() => {
console.log('5');
});
})
.then(() => {
console.log('6');
});
new Promise((resolve, reject) => {
console.log('7');
resolve();
}).then(() => {
console.log('8');
});
// 1 7 2 3 8 4 6 5 0
注意:then 中函数是否放入微任务队列取决于它前面的 Promise 的状态改变。