首页 > 其他分享 >[JS] promise知识点与应用场景

[JS] promise知识点与应用场景

时间:2024-07-05 19:08:18浏览次数:13  
标签:知识点 resolve const error JS promise reject Promise

Promise是JS中用于处理异步操作的方法,- 支持链式调用从而解决了地狱回调问题。

Promise的基础用法

状态

promise有三种状态:

  • Pending(待定):初始状态,既不是成功也不是失败。
  • Fulfilled(已成功):操作成功完成。
  • Rejected(已失败):操作失败。
const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (成功) {
    resolve(value);
  } else {
    reject(error);
  }
});

实例方法

Promise有三个实例方法,分别是thencatch,和finally

  • then用于处理Promise成功的情况:
promise.then((value) => {
  console.log(value);
});
  • catch用于处理Promise失败的情况,即异常捕获:
promise.catch((error) => {
  console.error(error);
});
  • finally:无论Promise最终状态如何(成功或失败),都会执行finally中的回调。
promise.finally(() => {
  console.log('操作完成');
});

链式调用

then方法可以返回一个Promise,并在后续链式地继续调用then方法。

doSomething()
  .then((result) => {
    return doSomethingElse(result);
  })
  .then((newResult) => {
    return doThirdThing(newResult);
  })
  .then((finalResult) => {
    console.log(`Final result: ${finalResult}`);
  })
  .catch((error) => {
    console.error(error);
  });

链式调用只需要在尾部调用一次catch,在链式调用的过程中发生的异常都会被这个尾部的catch捕获。

静态方法

  • Promise.resolve(value):返回一个成功的Promise,值为value;常见于后面跟上then方法将一个函数推入微任务队列;
  • Promise.reject(reason):返回一个失败的Promise,原因为reason
  • Promise.all(iterable):并行执行多个Promise,所有Promise都成功时返回一个包含所有结果的新Promise,如果有任何一个失败,则返回失败的Promise。
Promise.all([promise1, promise2, promise3])
  .then((values) => console.log(values))
  .catch((error) => console.error(error));
  • Promise.race(iterable):返回第一个完成的Promise,无论成功还是失败。
Promise.race([promise1, promise2, promise3])
  .then((value) => console.log(value))
  .catch((error) => console.error(error));

Promise.all的应用场景

并发请求,有时候在一个页面中需要使用多个GET请求获取页面数据并渲染,并且这些GET请求没有依赖关系,即不需要考虑请求顺序。那么这时就可以使用Promise.all并发执行这些GET请求。

const fetchUser = fetch('https://api.example.com/user');
const fetchPosts = fetch('https://api.example.com/posts');
const fetchComments = fetch('https://api.example.com/comments');

Promise.all([fetchUser, fetchPosts, fetchComments])
  .then(([userResponse, postsResponse, commentsResponse]) => {
    return Promise.all([userResponse.json(), postsResponse.json(), commentsResponse.json()]);
  })
  .then(([userData, postsData, commentsData]) => {
    console.log(userData, postsData, commentsData);
  })
  .catch((error) => {
    console.error('请求失败', error);
  });

并发执行需要注意并发量不要太大,我们可以通过实现一个并发控制的类来限制并发量。

class RequestScheduler {
  constructor(concurrencyLimit) {
    this.concurrencyLimit = concurrencyLimit;
    this.running = 0;
    this.queue = [];
  }

  // 添加请求到队列
  add(requestFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ requestFn, resolve, reject });
      this.runNext();
    });
  }

  // 执行下一个请求
  runNext() {
    if (this.running >= this.concurrencyLimit || this.queue.length === 0) {
      return;
    }

    const { requestFn, resolve, reject } = this.queue.shift();
    this.running++;

    requestFn()
      .then((result) => {
        resolve(result);
      })
      .catch((error) => {
        reject(error);
      })
      .finally(() => {
        this.running--;
        this.runNext();
      });
  }
}

// 使用示例
const scheduler = new RequestScheduler(3); // 限制并发请求数量为3

const createRequest = (url) => () => fetch(url).then((response) => response.json());

const urls = [
  'https://jsonplaceholder.typicode.com/posts/1',
  'https://jsonplaceholder.typicode.com/posts/2',
  'https://jsonplaceholder.typicode.com/posts/3',
  'https://jsonplaceholder.typicode.com/posts/4',
  'https://jsonplaceholder.typicode.com/posts/5'
];

const requestPromises = urls.map((url) => scheduler.add(createRequest(url)));

Promise.all(requestPromises)
  .then((results) => {
    console.log('所有请求完成:', results);
  })
  .catch((error) => {
    console.error('请求失败:', error);
  });
  • createRequest方法生成返回Promise的请求函数;
  • scheduler.add方法将一个请求添加到调度器中,并在并发限制允许的情况下执行;
  • Promise.all的作用是等待所有请求完成,并且统一处理异常。

Promise.race的应用场景

Promise.race方法关注的是最快出结果(不管是fulfilled还是rejected)的promise,可以实现超时处理。
超时处理:在race中传入网络请求的promise和定时器的promise,如果网络请求在指定时间内到达则正常执行then流程,如果定时器先到达则表示超时,调用reject走catch流程。

const fetchWithTimeout = (url, timeout) => {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), timeout));
  return Promise.race([fetchPromise, timeoutPromise]);
};

fetchWithTimeout('https://api.example.com/data', 5000)
  .then((response) => response.json())
  .then((data) => {
    console.log('请求成功', data);
  })
  .catch((error) => {
    console.error('请求失败或超时', error);
  });

Promise.allSettled

Promise.allSettled 方法返回一个在所有给定的 Promise 已经 fulfilled 或 rejected 后的 Promise,并且带有一个对象数组,每个对象表示对应的 Promise 结果。
如果是fulfilled,则结果字段为value
如果是rejected,则结果字段为reason

const promises = [
  Promise.resolve('resolved'),
  Promise.reject('rejected'),
  new Promise((resolve) => setTimeout(resolve, 1000, 'pending resolved'))
];

Promise.allSettled(promises)
  .then((results) => {
    results.forEach((result) => console.log(result));
  });

// 输出:
// { status: 'fulfilled', value: 'resolved' }
// { status: 'rejected', reason: 'rejected' }
// { status: 'fulfilled', value: 'pending resolved' }

Promise.any

接受一个promise数组,返回一个promise。
和Promise.race不同,Promise.any会过滤掉所有rejected 的promise,而关注第一个fulfilled的promise的值。
如果数组中所有promise都被rejected的话,那么会返回一个AggregateError类型的实例,带有errors字段,是一个数组,指明了每一个promise的reason
应用场景:any可以用来在多个备用资源中获取最先成功响应的资源。
最快成功返回的备用资源:假设一个数据有多个可用来源,我们只需要拿到其中一个成功响应就可以了,那么肯定是想要拿最快返回的那一个,这个时候用any就很nice~

const loadImage = (url) => new Promise((resolve, reject) => {
  const img = new Image();
  img.onload = () => resolve(url);
  img.onerror = () => reject(new Error(`Failed to load image at ${url}`));
  img.src = url;
});

const imageUrls = ['image1.png', 'image2.png', 'image3.png'];
const imagePromises = imageUrls.map(loadImage);

Promise.any(imagePromises)
  .then((result) => {
    console.log('第一个加载完成的图片', result);
  })
  .catch((error) => {
    console.error('所有图片加载失败', error);
  });

Promise.withResolvers

这个方法返回一个新的promise对象和用于解决或拒绝它的resolvereject方法。
可以简单地使用Promise手动实现:

Promise.withResolvers = function() {
  let resolve, reject;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
};

使用 Promise.withResolvers() 关键的区别在于解决和拒绝函数现在与 Promise 本身处于同一作用域,而不是在执行器中被创建和一次性使用。
通常在一些重复事件中使用,例如在处理流数据或者队列的时候,在这些场景下通常可以减少嵌套,优化代码结构。
这里介绍MDN上面的案例:将流转换为异步可迭代对象。

// 定义 async generator 函数 readableToAsyncIterable,将流转换为异步可迭代对象
async function* readableToAsyncIterable(stream) {
  // 创建 Promise 和解析器对象
  let { promise, resolve, reject } = Promise.withResolvers();

  // 监听流的错误事件,一旦出错则调用 reject 方法
  stream.on("error", (error) => reject(error));

  // 监听流的结束事件,一旦结束则调用 resolve 方法
  stream.on("end", () => resolve());

  // 监听流的可读事件,一旦流准备好可以读取则调用 resolve 方法
  stream.on("readable", () => resolve());

  // 循环处理流中的数据块,直到流不再可读
  while (stream.readable) {
    // 等待当前的 Promise 解决
    await promise;

    let chunk;
    // 循环读取流中的数据块
    while ((chunk = stream.read())) {
      // 生成数据块
      yield chunk;
    }

    // 获取新的 Promise 和解析器对象,以便下一轮循环使用
    ({ promise, resolve, reject } = Promise.withResolvers());
  }
}

创建一个简单的可读流测试一下:

const { Readable } = require('stream');

// 测试函数
async function testReadableToAsyncIterable() {
  // 创建一个简单的可读流
  const data = ['Hello', 'World'];
  const readableStream = Readable.from(data);

  // 将可读流转换为异步可迭代对象
  const asyncIterable = readableToAsyncIterable(readableStream);

  // 使用 for await...of 循环遍历异步可迭代对象中的数据块,并验证结果
  let result = '';
  for await (const chunk of asyncIterable) {
    result += chunk.toString();
  }

  // 断言结果是否符合预期
  if (result === data.join('')) {
    console.log('测试通过:数据正常读取和处理。');
  } else {
    console.error('测试失败:数据读取和处理出现问题。');
  }
}

// 执行测试函数
testReadableToAsyncIterable();

Promise规范与手写Promise

标签:知识点,resolve,const,error,JS,promise,reject,Promise
From: https://www.cnblogs.com/feixianxing/p/18286001/javascript-promise

相关文章

  • .NET控制台读取appsettings.json,配置日志
    需要安装nuget包Microsoft.Extensions.Configuration 、Microsoft.Extensions.Configuration.FileExtensions、Microsoft.Extensions.Configuration.Json、NLogusingNLog;usingNLog.Config;usingMicrosoft.Extensions.Configuration;namespaceConsoleApp2{int......
  • nodejs 安装使用ip2region - 实时精准的IP地址到区域运营商查询
    ip2region简介ip2region是一个高性能且高准确度的离线IP地址定位库和IP定位数据管理框架。它能够根据IP地址解析出对应的位置信息,包括国家、地区、省份、城市以及互联网服务提供商(ISP)。以下是ip2region的一些关键特性:高准确率:它声称有99.9%的准确率,这使得它......
  • 初学vue3, 全是黑盒子,vue3知识点汇总
    学习Vue.js应该像学习一门编程语言一样,首先要熟练掌握常用的知识,而对于不常用的内容可以简单了解一下。先对整个框架和语言有一个大致的轮廓,然后再逐步补充细节。千万不要像学习算法那样,一开始就钻牛角尖。前序:vueAPI的风格分为:选项式和组合式,vue2中一般用选项式,所......
  • JS之基础
    近些年来,一直从事后端开发,只有刚开始入行的时候,还是前后端不分离的时代,就是一套组合:jQuery+PHP后来的工作中就是前后端分离的开发模式,也就专心从事后端的开发,几经变更,后端语言也涉猎了很多,但是前端的技术已经是日新月异.有些语法或者基础都已经不熟悉了,至此总结,......
  • C# 去除JSON的空节点
    方法一:usingNewtonsoft.Json.Linq;usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;JObjecttestJson=newJObject(){{"code","1234560"},{"app",null}};testJson.DescendantsAndSelf().......
  • 【Unity几种数据存储之间的区别】PlayerPrefs、Json、XML、二进制、SQLite数据存储之
    ......
  • 使用Terraform极速部署Next.js网站到S3
    「AWS?好像很难懂……」「试过用AWS,但按钮太多,搞不清楚……」「Terraform?没听说过……」其实,直到最近我也是这样想的。但即使是我,也能使用Terraform构建网站。在本文中,我将分享如何使用AWS和Terraform将Next.js网站部署到S3的过程!用到的工具TerraformNode.jsAWSCLI创建并......
  • (四)JS逆向——中国观鸟网
    爬取观鸟网的信息 有sign值,timestamp和requestid,要看这些值是怎么生成的 载荷有加密的数据 返回值也经过加密 搜索requestid,找到了eval加密的代码,通过解密,就能找到生成这些值的代码段 代码格式化后,找到了这几个值的生成位置 requestid的生成是随机值,timestamp......
  • threejs入门2:Creating a scene
    参考:https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-sceneThegoalofthissectionistogiveabriefintroductiontothree.js.Wewillstartbysettingupascene,withaspinningcube.Aworkingexampleisprovidedatthebottomof......
  • windows安装以及切换使用nodejs多版本
    1安装nvmnvm是一个简单的bash脚本,它是用来管理系统中多个已存的Node.js版本。可以先把系统已有的node卸载掉,也可不卸载,但是以防没必要的冲突,尽量还是卸掉。1.1下载nvm下载地址:https://github.com/coreybutler/nvm-windows/releases,下载.zip后缀的这个文件,下载后解压安装即可......