跳到主要内容

Promise

对Promise的理解

Promise 是 JavaScript 中的一个对象,用于处理异步操作。它提供了一种结构化的方式来处理异步代码,解决了回调地狱(Callback Hell)的问题,使得异步编程更加简洁和可读。

Promise 的特点:

  1. 异步操作的状态管理:Promise 有三种状态,分别是 pending(进行中)、fulfilled(已完成)和 rejected(已拒绝)。状态一旦确定,就不可变更。
  2. 异步操作的结果处理:Promise 可以通过 then() 方法处理异步操作的成功结果,并使用 catch() 方法处理异常或拒绝的情况。
  3. 链式调用:Promise 的 then() 方法返回一个新的 Promise 对象,可以实现链式调用,使得异步操作可以按照特定的顺序进行处理。
  4. 错误冒泡:Promise 具有错误冒泡的特性,如果一个 Promise 链中的某个 Promise 被拒绝,将会一直向后传递直到被捕获或到达链的末尾。

Promise 的优点:

  1. 更好的可读性:Promise 的链式调用可以使异步操作的代码更加直观和可读,避免了回调地狱的嵌套。
  2. 更好的错误处理:通过 catch() 方法可以集中处理 Promise 链中的错误,提供了更好的错误处理机制。
  3. 更好的扩展性:Promise 可以与其他异步编程模式(如 Async/Await、Generator)结合使用,提供了更灵活的异步编程方式。

Promise 的缺点:

  1. 无法取消:一旦 Promise 被创建,无法取消或中断其执行。这可能会导致无谓的异步操作执行,浪费资源。
  2. 无法处理多个异步操作的并发:Promise 的链式调用只能处理一系列的异步操作,无法很方便地处理多个异步操作的并发情况。

Promise 解决的问题:

Promise 解决了传统回调函数编程中的回调地狱问题。回调地狱指的是在多个异步操作嵌套执行的情况下,回调函数的层层嵌套造成代码可读性差、难以维护的情况。Promise 提供了链式调用的方式,使得异步操作可以按照顺序进行处理,代码结构更加清晰和可维护。此外,Promise 还提供了更好的错误处理机制,简化了错误处理的代码。

手写promise

首先得知道A+规范

// 自定义Promise模块,IIFE
(function (window) {
// Promise的三种状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise {
constructor(executor) {
// 保存状态
this.status = PENDING;
// 保存结果
this.value = undefined;
// 保存决绝的原因
this.reason = undefined;
// 保存callback
this.onfulfilledCallbacks = [];
this.onrejectedCallbacks = [];

const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onfulfilledCallbacks.forEach((callback) => {
callback(this.value);
});
}
};

const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onrejectedCallbacks.forEach((callback) => {
callback(this.reason);
});
}
};

try {
// 执行 executor 函数,并将 resolve 和 reject 函数作为参数传递
executor(resolve, reject);
} catch (error) {
// 如果执行过程中出现异常,则将 Promise 状态转换为 rejected
reject(error);
}
}

then(onfulfilled, onrejected) {
// 保证onfulfilled, onrejected是函数
onfulfilled =
typeof onfulfilled === "function" ? onfulfilled : (value) => value;
onrejected =
typeof onrejected === "function"
? onrejected
: (reason) => {
console.log(123456);
throw reason;
};
const promise2 = new Promise((resolve, reject) => {
const onfulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = onfulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};

const onrejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = onrejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
if (this.status === FULFILLED) {
onfulfilledMicrotask();
} else if (this.status === REJECTED) {
onrejectedMicrotask();
} else if (this.status === PENDING) {
this.onfulfilledCallbacks.push(onfulfilledMicrotask);

this.onrejectedCallbacks.push(onrejectedMicrotask);
}
});
return promise2;
}

catch(onrejected) {
return this.then(null, onrejected);
}

static resolve(value){
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(resolve, reject);
} else {
resolve(value);
}
});
}

static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}

static all(promises){
const values = Array.from(promises.length)
let fulfilledCount = 0;
return new Promise((resolve, reject) => {
promises.forEach((p, index)=>{
Promise.resolve(p).then((value)=>{
values[index] = value;
fulfilledCount++;
if(fulfilledCount === promises.length){
resolve(values);
}
}, (reason)=>{
reject(reason);
})
})
});
}

static race(promises){
return new Promise((resolve, reject) => {
for(let i = 0; i < promises.length; i++){
Promise.resolve(promises[i]).then((value)=>{
resolve(value);
}, (reason)=>{
reject(reason);
})
}
});
}
}
const resolvePromise = (promise2, x, resolve, reject) => {
// console.log(reject,'123');
if (promise2 === x) {
reject(new TypeError("循环引用"));
} else if (x instanceof Promise) {
x.then(
(value) => {
resolvePromise(promise2, value, resolve, reject);
},
(reason) => {
reject(reason);
}
);
} else {
resolve(x);
}
};
window.Promise = Promise;
})(window);

FAQ?

Promise中,resolve后面的语句是否还会执行?

会被执行。如果不需要执行,需要在 resolve 语句前加上 return。

和 async/await 有什么关系?

es2017的新语法,async/await就是generator + promise的语法糖

async/await 和 Promise 的关系非常的巧妙,await必须在async内使用,并装饰一个Promise对象,async返回的也是一个Promise对象。

async/await中的return/throw会代理自己返回的Promise的resolve/reject,而一个Promise的resolve/reject会使得await得到返回值或抛出异常。

  • 如果方法内无await节点
    • return 一个字面量则会得到一个{PromiseStatus: resolved}的Promise。
    • throw 一个Error则会得到一个{PromiseStatus: rejected}的Promise。
  • 如果方法内有await节点
    • async会返回一个{PromiseStatus: pending}的Promise(发生切换,异步等待Promise的执行结果)。
    • Promise的resolve会使得await的代码节点获得相应的返回结果,并继续向下执行。
    • Promise的reject 会使得await的代码节点自动抛出相应的异常,终止向下继续执行。

如何中断Promise?

Promise 有个缺点就是一旦创建就无法取消,所以本质上 Promise 是无法被终止的,但我们在开发过程中可能会遇到下面两个需求:

中断调用链

就是在某个 then/catch 执行之后,不想让后续的链式调用继续执行了。

somePromise
.then(() => {})
.then(() => {
// 终止 Promise 链,让下面的 then、catch 和 finally 都不执行
})
.then(() => console.log('then'))
.catch(() => console.log('catch'))
.finally(() => console.log('finally'))

一种方法是在then中直接抛错, 这样就不会执行后面的then, 直接跳到catch方法打印err(但此方法并没有实际中断)。但如果链路中对错误进行了捕获,后面的then函数还是会继续执行。

当新对象保持“pending”状态时,原Promise链将会中止执行。

Promise.resolve().then(() => {
console.log('then 1')
return new Promise(() => {})
}).then(() => {
console.log('then 2')
}).then(() => {
console.log('then 3')
}).catch((err) => {
console.log(err)
})

中断Promise

注意这里是中断而不是终止,因为 Promise 无法终止,这个中断的意思是:在合适的时候,把 pending 状态的 promise 给 reject 掉。例如一个常见的应用场景就是希望给网络请求设置超时时间,一旦超时就就中断,我们这里用定时器模拟一个网络请求,随机 3 秒之内返回。

function timeoutWrapper(p, timeout = 2000) {
const wait = new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时')
}, timeout)
})
return Promise.race([p, wait])
}

let abortPromise = new Promise((resolve, reject) => {
// 中断操作,例如取消请求等
// 这里可以是任何异步操作,比如取消请求、关闭连接等
setTimeout(() => {
reject(new Error('Promise aborted'));
}, 1000);
});

let originalPromise = new Promise((resolve, reject) => {
// 执行一些异步操作
// 这里可以是任何异步操作,比如发送请求等
setTimeout(() => {
resolve('Promise resolved');
}, 2000);
});

Promise.race([originalPromise, abortPromise])
.then(result => {
console.log(result); // Promise resolved
})
.catch(error => {
console.log(error.message); // Promise aborted
});

使用可取消的 Promise 库:一些第三方的 Promise 库提供了支持 Promise 取消的功能,比如 Bluebird 库的 Promise.cancel() 方法。通过使用这些库,可以在需要时中断 Promise。