一、期约
1.1、期约
期约有三种状态:Pending、fulfilled、rejected等,状态是私有的,不能直接检测到,也不能被外部js代码修改。期约状态一旦非Pending了,就不能转换了。
执行器函数:主要负责初始化期约的异步行为和控制状态的最终转换。控制状态主要是依靠resolve和reject函数实现。执行器函数是同步执行
的。
js
let p = new Promise((resolve, reject) => { reject() });
let p1 = Promise.resolve("a");
let p2 = Promise.reject("c"); // Uncaught (in promise) c
let p3 = Promise.resolve(p2);
console.log(p3);// Promise rejected {<rejected>: 'c'}
let p4 = Promise.resolve(p1);
console.log(p4);// Promise fulfilled {<fulfilled>: 'a'}
let p5 = Promise.reject(p1);
console.log(p5);// Promise rejected {Promise {<fulfilled>: 'a'}}
console.log(Promise.reject(Promise.resolve(" b")));//Promise rejected {Promise {<fulfilled>: 'b'}}
期约嵌套时:
如果是resolve嵌套resolve那就是幂等逻辑,
如果是reject嵌套resolve那状态就是reject,拒绝期约的理由就是Promise<fulfilled>
。
如果是resolve嵌套reject,结果还是reject的,翻转不了状态。
1.2、then(resolve,reject)返回新的期约
js
let p = Promise.reject("foo");
let p1 = p.then(null, () => Error("baz"));
setTimeout(console.log, 0, p1);// Promise {<fulfilled>: Error: baz
let p2 = p.then(null, () => {throw 'aaa'});
setTimeout(console.log, 0, p2);// Promise {<rejected>: 'aaa'}
在这个例子中,p1
是通过 p
的 then
方法创建的,该方法中使用了一个拒绝处理函数,但是拒绝处理函数返回了一个新的 Error
对象而不是一个 Promise 对象。根据 Promise 的规范,如果拒绝处理函数返回一个非 Promise 值(比如一个普通的对象、字符串、数字等),则会将这个非 Promise 值作为成功的值来解析,并且返回一个状态为已解决(fulfilled)的 Promise 对象,其值就是拒绝处理函数的返回值。
js
let p = Promise.reject("foo");
let p1 = p.then(null, () => { });
let p2 = p.then(null, () => undefined);
let p3 = p.then(null, () => Promise.resolve());
setTimeout(console.log, 0, p1);
setTimeout(console.log, 0, p2);
setTimeout(console.log, 0, p3);
let a = Promise.resolve("foo");
let a1 = a.then(() => { });
let a2 = a.then(() => undefined);
let a3 = a.then(() => Promise.resolve());
setTimeout(console.log, 0, a1);
setTimeout(console.log, 0, a2);
setTimeout(console.log, 0, a3);
以上打印的都是Promise {<fulfilled>: undefined}
Promise.prototype.catch(onReject)就相当于then(null, onReject);
Promise.prototype.finally()则是在转换为解决或者拒绝时执行,多数情况表现为父期约的传递
。
js
let p = Promise.reject("foo");
let p1 = p.finally(null, () => { });
let p2 = p.finally(null, () => undefined);
let p3 = p.finally(null, () => Promise.resolve());
setTimeout(console.log, 0, p1);// Promise {<rejected>: 'foo'}
setTimeout(console.log, 0, p2);// Promise {<rejected>: 'foo'}
setTimeout(console.log, 0, p3);// Promise {<rejected>: 'foo'}
let a = Promise.resolve("foo");
let a1 = a.finally(() => { });
let a2 = a.finally(() => undefined);
let a3 = a.finally(() => Promise.resolve());
setTimeout(console.log, 0, a1);// Promise {<fulfilled>: 'foo'}
setTimeout(console.log, 0, a2);// Promise {<fulfilled>: 'foo'}
setTimeout(console.log, 0, a3);// Promise {<fulfilled>: 'foo'}
let a4= a.finally(() => {throw "aa"});
setTimeout(console.log, 0, a4);// Promise {<rejected>: 'aa'}
Promise 的非重入特性指的是 Promise 的执行过程是不可中断的,一旦开始执行,就会一直执行到结束,并且期间不会被其他代码打断。具体来说,当一个 Promise 开始执行时,它会一直执行,直到完成(resolve)或拒绝(reject)。在执行过程中,无法中断 Promise 的执行,也无法将其暂停或取消。即使在 Promise 执行过程中发生了异步操作(例如定时器、异步请求等),这些异步操作也无法中断 Promise 的执行,Promise 会一直保持执行状态,直到异步操作完成。
这种特性保证了 Promise 的可靠性和稳定性,确保 Promise 的状态不会被外部因素干扰,从而避免了竞态条件和不确定性的出现。同时,由于 Promise 的执行是非重入的,所以在执行期间无法在 Promise 中插入其他任务或代码片段,保证了 Promise 的原子性和一致性。
在 Java 中,非重入通常指的是一种锁的行为特性。非重入锁是一种锁机制,它不允许同一个线程多次获取同一个锁。换句话说,如果一个线程已经持有了某个锁,再次尝试获取该锁会导致线程阻塞或抛出异常。
在 Java 中,常见的非重入锁包括使用 synchronized
关键字定义的同步块以及 ReentrantLock
类的非公平锁模式。这些锁在同一个线程多次尝试获取同一个锁时,会阻塞该线程或抛出异常,以避免锁的重入。非重入锁的设计有助于避免并发编程中的死锁和饥饿问题,但同时也会增加代码的复杂性和性能开销。
js
let a = Promise.resolve("foo");
a.then(() => { console.log("a resolved") });
console.log("test ")
日志打印这里也是test先打印,后续才打印a resolved,因为处理程序会等到运行的消息队列让它出列时才执行。
1.3、传递解决值和拒绝理由
js
let a = Promise.resolve("foo");
a.then((value) => { console.log(value) }); // foo
let b = Promise.reject("baz");
b.catch((value) => { console.log(value) });// baz
1.4、期约拒绝
js
let b = Promise.reject("baz");
b.catch((value) => console.log(value)) // baz
.then(() => console.log("resolved"));// resolved
为啥catch之后的then只写了resolve方法也能打印,是因为catch方法的返回值是非 Promise 值,那么就会作为成功的值来解析,并且返回一个状态为已解决(fulfilled)的 Promise 对象,后面使用then就可以捕捉了.
1.5、期约连锁与期约合成
then、catch、finally使用。
期约图:有向无环图图,就是二叉树,层序遍历。期约的处理程序是先添加到消息队列,然后才逐个执行,因此构成层序遍历。
js
let A = new Promise((resolve, reject) => {
console.log("A");
resolve();
})
let B = A.then(() => console.log("B"));
let C = A.then(() => console.log("C"));
let D = B.then(() => console.log("D"));
let E = B.then(() => console.log("E"));
// A B C D E F
// A
// B C
// D E
1.6、Promise.all和Promise.race
js
let all = Promise.all([
Promise.resolve(3),
new Promise((resolve, reject) => {
console.log("a")
setTimeout(resolve, 2000);
})
])
let p = all.then(() => console.log("b"));
a打印之后,间隔2s才打印b
js
let all = Promise.all([
Promise.reject(3),
new Promise((resolve, reject) => {
console.log("a")
setTimeout(resolve, 2000);
})
])
let p = all.then(null, () => console.log("b"));
a打印之后,很快打印了b,因为只要有一个reject,那整个就reject,不会执行完后面resolve了。
js
let all = Promise.all([
new Promise((resolve, reject) => {
reject();
console.log("b");
}), Promise.reject(3)
])
let p = all.then(null, (value) => console.log(value)); // undefined
- 在 JavaScript 中,
Promise.all()
和Promise.race()
是两种常用的 Promise 组合方法,它们有以下区别:
Promise.all():
- 接收一个由多个 Promise 对象组成的可迭代对象(如数组),并返回一个新的 Promise 对象。
- 当传入的所有 Promise 对象都成功(即状态变为 resolved)时,返回的 Promise 对象的状态变为 resolved,并且将所有 Promise 对象的结果组成的数组作为值传递给成功的回调函数。
- 如果传入的任何一个 Promise 对象失败(即状态变为 rejected),则返回的 Promise 对象的状态会立即变为 rejected,并且失败的 Promise 对象的结果会作为值传递给失败的回调函数。
Promise.race():
- 接收一个由多个 Promise 对象组成的可迭代对象(如数组),并返回一个新的 Promise 对象。
- 当传入的多个 Promise 对象中有任意一个状态发生变化时(无论是成功还是失败),返回的 Promise 对象的状态就会立即变为与该变化一致,并且将第一个状态变化的 Promise 对象的结果作为值传递给对应的回调函数。
简而言之,Promise.all()
用于等待多个 Promise 对象全部完成,然后返回一个包含所有结果的新 Promise 对象;而 Promise.race()
则是用于等待多个 Promise 对象中的任意一个完成,并返回第一个完成的 Promise 对象的结果。
1.7、串行期约合成
js
function add3(x) {
return x + 3;
}
function add5(x) {
return x + 5;
}
function add7(x) {
return x + 7;
}
function add15(x) {
return add3(add5(add7(x)));
}
function add15(x) {
return Promise.resolve(x).then(add3).then(add5).then(add7);
}
add15(0).then((x) => console.log(x));// 15
function add15(x) {
return [add3, add5, add7].reduce((promise, fn) => promise.then(fn), Promise.resolve(x));
}
add15(0).then((x) => console.log(x));// 15
function compose(...fns) {
return (x) => fns.reduce((promise, fn) => promise.then(fn), Promise.resolve(x));
}
compose(add3,add5,add7)(10).then(console.log); // 25
来看看最后一个方法compose,返回是一个函数,那这个compse调用 compose(add3,add5,add7)返回一个函数,函数的参数传入10结果就是25;
1.8、期约的拓展之取消
Promise规范里期约是不能取消的,但是可以模拟取消。
js
class CancelToken {
constructor(cancelFn) {
this.promise = new Promise((resolve, reject) => {
console.log("aaa")
cancelFn(() => {
setTimeout(console.log, 0, "delay cancelled");
resolve();
});
});
}
}
const startButton = document.querySelector("#start");
const cancelButton = document.querySelector("#cancel");
function cancellable(delay) {
console.log("set delay");
return new Promise((resolve, reject) => {
const id = setTimeout((() => {
console.log("delay resolve");
resolve();
}), delay);
const cancelToken = new CancelToken((callback) => {
cancelButton.addEventListener("click", callback);
})
cancelToken.promise.then(() => clearTimeout(id));
})
}
startButton.addEventListener("click", () => cancellable(5000));
点击start按钮,创建了一个期约A,期约A初始化时定义了一个5s的定时器,同时创建了一个CancelToken对象,给取消按钮设置了监听,且CancelToken对象创建时会创建一个期约B。一旦取消按钮点击,就会触发B期约解决,就会触发clearTimeout,那么A期约就一直是一个Pending状态。
1.9、期约的拓展之进度通知
期约是不支持进度追踪的,但是可以通过拓展实现。
js
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(handler) {
this.notifyHandlers.push(handler);
return this;
}
}
let p = new TrackablePromise((resolve, reject, notify) => {
function countDown(x) {
if (x > 0) {
notify(`${20 * x}%remanining`);
setTimeout(() => countDown(x - 1), 1000);
} else {
resolve();
}
}
countDown(5);
})
p.notify((x) => setTimeout(console.log, 0, "progress", x));
p.then(() => setTimeout(console.log, 0, "complete"));
handler
是一个函数,用于处理任务的进度通知。当执行器函数中调用notify
方法时,会将该函数作为参数传入,并存储在notifyHandlers
数组中。当任务的进度发生变化时,会依次调用notifyHandlers
数组中的每个处理函数,将当前任务的状态传递给它们。status
是描述任务进度的消息。在执行器函数中调用notify
方法时,会将当前任务的状态作为参数传入,并传递给通知处理函数。在这个例子中,任务的状态是一个表示任务剩余进度的消息字符串,例如"20% remaining"
。
因此,handler
是用于处理任务进度通知的函数,而 status
是描述任务进度的消息。
二、异步函数
2.1、异步函数async、await
async就只是一个标志符,真正起作用的是await。javaScript运行时在碰到await关键字时,会记录在哪里暂停执行。等到await右边的值可用了,Javascript运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。
js
async function foo() {
}
foo().then((i) => console.log(i)); // undefined
async function bar() {
return 1;
}
bar().then((i) => console.log(i));// 1
async修饰的函数,返回值会包装成一个fulfilled的期约返回。
js
async function foo1() {
throw 4;
}
foo1().catch((i) => console.log(i)); // 4
async function foo() {
Promise.reject(3);
console.log(5); // 5
}
foo().catch((i) => console.log(i)); // Uncaught (in promise) 3
async function bar() {
await Promise.reject(4);
console.log(6); // 这个不执行
}
bar().catch((i) => console.log(i));// 4
拒绝的期约可以被await 解包之后,被catch捕获。
在 bar
函数中,await
关键字会暂停函数的执行,直到 Promise 被解决或拒绝。在这种情况下,Promise.reject(4)
被拒绝了,所以 await
后面的代码不会执行。因此,console.log(6)
不会被执行。
js
async function foo(){
console.log(await Promise.resolve(9));
}
async function bar(){
console.log(await 8);
}
foo();
bar();
执行顺序打印的是9 8,而不是8 9 。
来看看kt和js中使用async和await的例子:
js
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function fetchUserData() {
await delay(1000); // 模拟耗时操作
return "John Doe";
}
async function main() {
try {
console.log("Waiting for user data...");
const userData = await fetchUserData(); // 等待异步操作的结果
console.log("User data received:", userData);
} catch (error) {
console.error("Error fetching user data:", error);
}
}
main();
js
import kotlinx.coroutines.*
fun main() {
// 启动一个协程
GlobalScope.launch {
val result = async { fetchUserData() } // 启动一个异步任务
println("Waiting for user data...")
val userData = result.await() // 等待异步任务的结果
println("User data received: $userData")
}
Thread.sleep(2000) // 主线程等待协程执行
}
suspend fun fetchUserData(): String {
delay(1000) // 模拟耗时操作
return "John Doe"
}
怎么感觉kt还复杂一些,用了suspend呢。
2.2、异步函数策略
2.2.1、实现sleep
js
function sleep(delay) {
return new Promise((resolve, reject) => {
setTimeout(resolve, delay);
})
}
async function foo() {
await sleep(1000)
console.log("job");
}
foo();
2.2.2、串行执行期约与利用平行执行
js
async function delay(id) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`${id} finished`);
resolve();
}, 1000)
})
}
async function foo(){
await delay(1);
await delay(2);
await delay(3);
await delay(4);
console.log("complete");
}
foo();
js
async function delay(id) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`${id} finished`);
resolve();
}, 1000)
})
}
async function foo(){
const p1 = delay(1);
const p2 = delay(2);
const p3 = delay(3);
const p4 = delay(4);
await p1;
await p2;
await p3;
await p4;
console.log("complete");
}
foo();
第一个例子串行执行期约。
第二个例子可以实现平行加速,因为预先创建Promise,在创建p1、p2、p3、p4等已经开始setTimeout了,await就只是取值而已。