js红书 【5 Promise】

katsu 发布于 2025-02-17 886 次阅读


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 的一种语法糖,用来让异步代码看起来像同步代码,从而简化异步编程的复杂度。以下是它的基础语法和核心内容:

  1. 声明异步函数:
    使用 async 关键字声明一个函数,这个函数会自动返回一个 Promise,即使你直接返回的是一个非 Promise 值,也会被包装为 Promise.resolve(返回值)

    js

    复制

    async function example() { return "Hello"; } // 调用 example() 会返回一个 Promise,最终解析为 "Hello"

  2. 使用 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 来捕获。
  3. 错误处理:
    异步函数内部的错误会导致返回的 Promise 被拒绝,因此通常使用 try-catch 来捕获错误。

    js

    复制

    async function getData() { try { let result = await someAsyncOperation(); return result; } catch (error) { console.error("Error:", error); throw error; // 或者处理错误后返回默认值 } }

  4. 并行执行:
    当需要同时执行多个异步操作时,可以使用 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 的异步;非常抽象