11 期约 promise与异步函数
异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。
为了让后续代码能够使用 x,异步执行的函数需要在更新 x 的值以后通知其他代码。如果程不需 要这个值,那么就只管继续执行,不必等待这个结果了。
我的理解
同步按顺序往下做 异步碰到异步的地方, 因为不知道什么时候返回结果,就先往下去做,知道挂起来的地方做完了,当中有要用到的地方也挂起来,直到拿到通知说前面异步的地方做完了
↑上面这个理解存在一定的误区 promise 出结果以后只是把 then 推到任务队列 不是立即执行
碰到 await 异步函数 会暂时退出,直到右边值可用再重新加回任务队列,没好的时候先进行别的同步操作
以前=回调地狱
settimeout 只是设置过多久推进任务队列,并不是真的执行结束的时间
假设 setTimeout 操作会返回一个有用的值。有什么好办法把这个值传给需要它的地方?广泛接受 的一个策略是给异步操作提供一个回调函数,这个回调中包含要使用异步返回值的代码
promise 我讨厌期约这个傻逼翻译
Promise()创建异步的实例
let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>
promise 是一个有状态的对象:
待定(pending)
兑现(fulfilled,有时候也称为“解决”,resolved)
拒绝(rejected)
let p = new Promise((resolve, reject) => { setTimeout(reject,10000); //10秒后调用reject() // 执行函数的逻辑
});
setTimeout(console.log, 0, p); // Promise <pending>
setTimeout(console.log, 11000, p); // 11 秒后再检查状态 // (After 10 seconds) Uncaught error
// (After 11 seconds) Promise <rejected>
这里的同步代码之所以没有捕获期约抛出的错误,是因为它没有通过异步模式捕获错误。从这 里就可以看出期约真正的异步特性:它们是同步对象(在同步执行模式中使用),但也是异步执行模式 的媒介。
try/catch 块并不能捕获该错误
promise 的实现
几个重要的方法 对于实例 p 来说
p.then (解决,拒绝) 实现了 promise 以后各种状态下干什么
p.catch () 针对 rejected
p.finally 做完不管状态
function onResolved(id) {
setTimeout(console.log, 0, id, 'resolved');
}
function onRejected(id) {
setTimeout(console.log, 0, id, 'rejected');
}
let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000)); let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000));
p1.then(() => onResolved('p1'),
() => onRejected('p1'));
p2.then(() => onResolved('p2'),
() => onRejected('p2'));
//(3 秒后)
// p1 resolved // p2 rejected
p2.then(null, () => onRejected('p2'));
:onRejected 处理程返回的值也会被 Promise.resolve()
Promise.prototype.catch()方法用于给期约添加拒绝处理程。这个方法只接收一个参数:
onRejected 处理程。事实上,这个方法就是一个语法糖,调用它就相当于调用 Promise.prototype. then(null, onRejected)。
当 promise进入落定状态时,与该状态相关的处理程仅仅会被排期,而非立即执行 因此,跟在 then()后面的同步代码一定先于 处理程执行。
推进消息队列。但这个 处理程在当前线程上的同步代码执行完成前不会执行。
跟在添加这个处 理程的代码之后的同步代码一定会在处理程之前先执行。即使期约一开始就是与附加处理程关联 的状态,执行顺也是这样的。这个特性由 JavaScript 运行时保证,被称为“非重入”(non-reentrancy) 特性。
// 创建解决的期约
let p = Promise.resolve();
// 添加解决处理程序
// 直觉上,这个处理程序会等期约一解决就执行
p.then(() => console.log("onResolved handler"));
// 同步输出,证明then()已经返回
console.log("then() returns");
// 实际的输出:
// then() returns
// onResolved handler
let p = new Promise((resolve) => {
synchronousResolve = function() {
console.log('1: invoking resolve()');
resolve();
console.log('2: resolve() returns');
}; });
p.then(() => console.log('4: then() handler executes'));
synchronousResolve();
console.log('3: synchronousResolve() returns');
// 实际的输出:
// 1: invoking resolve()
// 2: resolve() returns
// 3: synchronousResolve() returns // 4: then() handler executes
同步代码全部执行完毕后,事件循环开始处理微任务队列
传递
let p1 = new Promise((resolve, reject) => resolve('foo'));
p1.then((value) => console.log(value)); // foo
let p2 = new Promise((resolve, reject) => reject('bar'));
p2.catch((reason) => console.log(reason)); // bar
连锁和合成 一个接一个/多个合并为一个
一个期约可以有任意多个处理程,所以期约连锁可以构建有向非循环图的结构
Promise.all()和 Promise.race()
function addTwo(x) {return x + 2;}
function addThree(x) {return x + 3;}
function addFive(x) {return x + 5;}
function compose(...fns) {
return (x) => fns.reduce((promise, fn) => promise.then(fn), Promise.resolve(x))
}
let addTen = compose(addTwo, addThree, addFive); addTen(8).then(console.log); // 18
异步函数 async/await
核心知识
avaScript 中的异步函数(async/await)是基于 Promise 的一种语法糖,用来让异步代码看起来像同步代码,从而简化异步编程的复杂度。以下是它的基础语法和核心内容:
-
声明异步函数:
使用async关键字声明一个函数,这个函数会自动返回一个 Promise,即使你直接返回的是一个非 Promise 值,也会被包装为Promise.resolve(返回值)。js
复制
async function example() { return "Hello"; } // 调用 example() 会返回一个 Promise,最终解析为 "Hello" -
使用
await等待 Promise:
在异步函数内部,可以使用await关键字来暂停函数的执行,等待一个 Promise 解析后再继续执行。这使得代码逻辑更加直观。js
复制
async function fetchData() { let response = await fetch("https://api.example.com/data"); let data = await response.json(); return data; }await后面跟的是一个 Promise,会暂停函数执行直到该 Promise 解析(resolved 或 rejected)。- 如果 Promise 被拒绝(rejected),则会抛出异常,可以用
try-catch来捕获。
-
错误处理:
异步函数内部的错误会导致返回的 Promise 被拒绝,因此通常使用try-catch来捕获错误。js
复制
async function getData() { try { let result = await someAsyncOperation(); return result; } catch (error) { console.error("Error:", error); throw error; // 或者处理错误后返回默认值 } } -
并行执行:
当需要同时执行多个异步操作时,可以使用Promise.all()来并行执行,而不是串行等待每个操作完成。js
复制
async function parallelTasks() { const [result1, result2] = await Promise.all([ task1(), task2() ]); return { result1, result2 }; }
核心内容总结:
- 简化异步代码:
async/await让你可以用类似同步的方式写异步代码,从而减少回调地狱,提高代码可读性和维护性。 - 自动返回 Promise: 所有用
async声明的函数都会返回一个 Promise,方便与其他基于 Promise 的代码结合使用。 - 错误处理机制: 通过
try-catch块,可以像同步代码一样捕获异步操作中的错误,而不必在每个 Promise 后面添加.catch()。 - 与并发操作的结合: 使用
Promise.all()等方法可以高效地处理多个并行的异步操作。
掌握这些基础知识,可以帮助你更好地理解和使用 JavaScript 的异步编程模型。
书本内容
异步函数,也称为“async/await”(语法关键字)
主要解决 promise 做完了怎么拿值的问题
async function foo() {}
let bar = async function() {};
let baz = async () => {};
class Qux { async qux() {}
}
步函数如果使用 return 关键字返回了值(如果没有 return 则会返回 undefined),这 个值会被 Promise.resolve()包装成一个 promise对象。
async function foo() { console.log(1); return 3;
}
// 给返回的期约添加一个解决处理程序
foo().then(console.log);
console.log(2);
// 1 // 2 // 3
在异步函数中抛出错误会返回拒绝的期约:
async function foo() { console.log(1); throw 3;}
使用 await关键字可以暂停异步函数代码的执行,等待 Promise解决。
// 异步打印"foo"
async function foo() {
console.log(await Promise.resolve('foo')); }
foo(); // foo
// 异步打印"bar"
async function bar() {
return await Promise.resolve('bar'); }
bar().then(console.log);
// bar
// 1000 毫秒后异步打印"baz" async function baz() {
await new Promise((resolve, reject) => setTimeout(resolve, 1000));
console.log('baz');
}
baz();
// baz(1000 毫秒后)
async function foo() {
console.log(await Promise.resolve('foo'));
}8
async function bar() {
console.log(await 'bar');
}
async function baz() {
console.log('baz');
}
foo();
bar();
baz();
// baz
// bar
// foo
要完全理解 await 关键字,必须知道它并非只是等待一个值可用那么简单。JavaScript 运行时在碰 到 await 关键字时,会记录在哪里暂停执行。等到 await 右边的值可用了,JavaScript 运行时会向消息 队列中推送一个任务,这个任务会恢复异步函数的执行。
因此,即使 await 后面跟着一个立即可用的值,函数的其余部分也会被异步求值。
我感觉多个 await 排队添加完任务队列以后,执行结构是栈,后进来的任务先出去(因为任务队列确实似乎队列,但是 js 执行调用本身是栈)
实现 sleep ()
过 sleep 时间再返回 resolve
async function sleep(delay) {
return new Promise((resolve) => setTimeout(resolve, delay));
}
async function foo() {
const t0 = Date.now();
await sleep(1500); // 暂停约 1500 毫秒 console.log(Date.now() - t0);
}
foo();
// 1502
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
- 在 foo() 中,
await Promise.resolve(8)出现在 console.log 的参数位置。整个console.log(await Promise.resolve(8))语句必须先求值其参数(也就是执行 await 表达式),求值完成后才会调用 console.log。此时,await 表达式在“解析参数”过程中就遇到 await,于是 foo() 将“等待后续的代码”(即:取到 await 表达式的结果后接着执行后面的console.log(9)以及 console.log(await …) 这条语句剩下的部分)包装成一个微任务。但这个微任务的入队会在整个表达式求值完成后才真正排队。 - 而在 bar() 中,
await 6是一个独立的语句,求值后立即将剩下的代码(即:console.log(await 6)完成后剩下的那一行,再加上后面的console.log(7))包装为微任务并排入队列。
结果就是:
- 虽然两个 async 函数都因 await 暂停了,但由于书写上的位置不同(一个是在表达式内部,一个是独立的语句),bar() 的后续代码的微任务更早入队于 foo() 的后续代码微任务。
TC39 对 await 后面是期约的情况如何处理做过一次修改。修改后,本例中的 Promise.resolve(8)只会生成一个 异步任务。因此在新版浏览器中,这个示例的输出结果为 123458967。实际开发中,对于并行的异步操作我们通常 更关注结果,而不依赖执行顺。——译者注
单线程 js 的异步;非常抽象
