当前位置: 首页 > news >正文

JavaScript异步简介|Promise快速入门

异步(Asynchronous, async)是与同步(Synchronous, sync)相对的概念。

异步 JavaScript 简介

异步编程技术使你的程序可以在执行一个可能长期运行的任务的同时继续对其他事件做出反应而不必等待任务完成。与此同时,你的程序也将在任务完成后显示结果。
image.png

同步编程

同步程序是按照我们书写代码的顺序一行一行地执行程序的。浏览器会等待代码的解析和工作,在上一行完成后才会执行下一行。这样做是很有必要的,因为每一行新的代码都是建立在前面代码的基础之上的。
**同步函数:**因为在函数返回之前,调用者必须等待函数完成其工作。
存在问题:如果一个同步函数消耗时间很长,只能等待不能做其他的事情。
解决方法

  • 通过调用一个函数来启动一个长期运行的操作
  • 让函数开始操作并立即返回,这样我们的程序就可以保持对其他事件做出反应的能力
  • 当操作最终完成时,通知我们操作的结果。

事件处理程序

事件处理程序实际上就是异步编程的一种形式:

  • 提供的函数(事件处理程序)将在事件发生时被调用(而不是立即被调用)。
  • 如果“事件”是“异步操作已经完成”,事件处理程序被用来通知调用者异步函数调用的结果,这就是一种异步的方式。

回调

回调函数是一个被传递到另一个函数中的会在适当的时候被调用的函数。
事件处理程序就是一种特殊类型的回调函数。
当回调函数本身需要调用其他同样接受回调函数的函数时,基于回调的代码会变得难以理解。(回调函数中调用回调函数)
当你需要执行一些分解成一系列异步函数的操作时,这将变得十分常见。例如下面这种情况:

function doStep1(init, callback) {const result = init + 1;callback(result);
}
function doStep2(init, callback) {const result = init + 2;callback(result);
}
function doStep3(init, callback) {const result = init + 3;callback(result);
}
function doOperation() {doStep1(0, (result1) => {doStep2(result1, (result2) => {doStep3(result2, (result3) => {console.log(`结果:${result3}`);});});});
}
doOperation();

由于以上这些原因,大多数现代异步 API 都不使用回调。
事实上,JavaScript 中异步编程的基础是 Promise

参考资料:
JavaScript 异步编程 | 菜鸟教程
异步 JavaScript 简介 - 学习 Web 开发 | MDN

使用 Promise

Promise 是现代 JavaScript 中异步编程的基础。它是一个由异步函数返回的对象,可以指示操作当前所处的状态。
在 Promise 返回给调用者的时候,操作往往还没有完成,但 Promise 对象提供了方法来处理操作最终的成功或失败。

使用fetch() API

全局 fetch() 方法用于发起获取资源的请求,它会返回一个会在请求响应后兑现的 promise。

const fetchPromise = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);console.log(fetchPromise);fetchPromise.then((response) => {console.log(`已收到响应:${response.status}`);
});console.log("已发送请求……");

than方法:将一个处理函数传递给 Promise 的 then() 方法。当(如果)获取操作成功时,Promise 将调用我们的处理函数,传入一个包含服务器的响应的 Response 对象。
完整的输出结果应该是这样的:

Promise { <state>: "pending" }
已发送请求……
已收到响应:200

已发送请求…… 的消息在我们收到响应之前就被输出了。与同步函数不同,fetch() 在请求仍在进行时返回,这使我们的程序能够保持响应性。响应显示了 200(OK)的状态码,意味着我们的请求成功了。

链式使用 Promise

我们想获得 JSON 格式的响应数据,所以我们会调用 Response 对象的 json() 方法。事实上,json() 也是异步的,因此我们必须连续调用两个异步函数。

const fetchPromise = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);fetchPromise.then((response) => {const jsonPromise = response.json();jsonPromise.then((json) => {console.log(json[0].name);});
});

前面说过,在回调中调用另一个回调会出现多层嵌套的情况,这种“回调地狱”使我们的代码难以理解,这不是也一样吗,只不过变成了用 then() 调用而已?
但 Promise 的优雅之处在于 then() 本身也会返回一个 Promise,这个 Promise 将指示 then() 中调用的异步函数的完成状态。这意味着可以把代码改写成这样:

const fetchPromise = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);fetchPromise.then((response) => response.json()).then((data) => {console.log(data[0].name);});

直接返回 json() 返回的 Promise,并在该返回值上调用第二个 then()。这被称为 Promise 链。
还有一件事要补充:我们需要在尝试读取请求之前检查服务器是否接受并处理了该请求。我们将通过检查响应中的状态码来做到这一点,如果状态码不是“OK”,就抛出一个错误:

const fetchPromise = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);fetchPromise.then((response) => {if (!response.ok) {throw new Error(`HTTP 请求错误:${response.status}`);}return response.json();}).then((json) => {console.log(json[0].name);});

错误捕获

fetch() API 可能因为很多原因抛出错误(例如,没有网络连接或 URL 本身存在问题),我们也会在服务器返回错误消息时抛出一个错误。
为了支持错误处理,Promise 对象提供了一个 catch() 方法。

  • 当异步操作_成功_时,传递给 then() 的处理函数被调用,
  • 当异步操作_失败_时,传递给 catch() 的处理函数被调用。

如果将 catch() 添加到 Promise 链的末尾,它就可以在任何异步函数失败时被调用。

const fetchPromise = fetch("bad-scheme://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);fetchPromise.then((response) => {if (!response.ok) {throw new Error(`HTTP 请求错误:${response.status}`);}return response.json();}).then((json) => {console.log(json[0].name);}).catch((error) => {console.error(`无法获取产品列表:${error}`);});

合并使用多个 Promise

你可能需要合并多个异步函数的调用,Promise API 为解决这一问题提供了帮助。
方式一:需要所有的 Promise 都得到实现,但它们并不相互依赖。在这种情况下,将它们一起启动然后在它们全部被兑现后得到通知会更有效率。
这里需要 Promise.all() 方法。它接收一个 Promise 数组,并返回一个单一的 Promise。
由Promise.all()返回的 Promise:

  • 当且仅当数组中_所有_的 Promise 都被兑现时,才会通知 then() 处理函数并提供一个包含所有响应的数组,数组中响应的顺序与被传入 all() 的 Promise 的顺序相同。
  • 会被拒绝——如果数组中有_任何一个_ Promise 被拒绝。此时,catch() 处理函数被调用,并提供被拒绝的 Promise 所抛出的错误。
const fetchPromise1 = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
const fetchPromise2 = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/not-found",
);
const fetchPromise3 = fetch("bad-scheme://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json",
);Promise.all([fetchPromise1, fetchPromise2, fetchPromise3]).then((responses) => {for (const response of responses) {console.log(`${response.url}${response.status}`);}}).catch((error) => {console.error(`获取失败:${error}`);});

方式二:需要一组 Promise 中的某一个 Promise 的兑现,而不关心是哪一个。
在这种情况下,你需要 Promise.any()。这就像 Promise.all(),不过在 Promise 数组中的任何一个被兑现时它就会被兑现,如果所有的 Promise 都被拒绝,它也会被拒绝。
在这种情况下,无法预测哪个获取请求会先被兑现。

const fetchPromise1 = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
const fetchPromise2 = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/not-found",
);
const fetchPromise3 = fetch("https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json",
);Promise.any([fetchPromise1, fetchPromise2, fetchPromise3]).then((response) => {console.log(`${response.url}${response.status}`);}).catch((error) => {console.error(`获取失败:${error}`);});

Promise相关概念

首先,Promise 有三种状态:

  • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。这是调用 fetch() 返回 Promise 时的状态,此时请求还在进行中。
  • 已兑现(fulfilled):意味着操作成功完成。当 Promise 完成时,它的 then() 处理函数被调用。
  • 已拒绝(rejected):意味着操作失败。当一个 Promise 失败时,它的 catch() 处理函数被调用。


有时我们用已敲定(settled)这个词来同时表示已兑现(fulfilled)和已拒绝(rejected)两种情况。
:::tips
注意:
这里的“成功”或“失败”的含义取决于所使用的 API:例如,fetch() 认为服务器返回一个错误(如 404 Not Found)时请求成功,但如果网络错误阻止请求被发送,则认为请求失败。
:::

async 和 await

async 关键字为你提供了一种更简单的方法来处理基于异步 Promise 的代码。

  1. 在一个函数的开头添加 async,就可以使其成为一个异步函数。
  2. 在异步函数中,在调用一个返回 Promise 的函数之前使用 await 关键字。这使得代码在该点上等待,直到 Promise 被完成,这时 Promise 的响应被当作返回值,或者被拒绝的响应被作为错误抛出。
async function fetchProducts() {try {// 在这一行之后,我们的函数将等待 `fetch()` 调用完成// 调用 `fetch()` 将返回一个“响应”或抛出一个错误const response = await fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",);if (!response.ok) {throw new Error(`HTTP 请求错误:${response.status}`);}// 在这一行之后,我们的函数将等待 `response.json()` 的调用完成// `response.json()` 调用将返回 JSON 对象或抛出一个错误const json = await response.json();console.log(json[0].name);} catch (error) {console.error(`无法获取产品列表:${error}`);}
}const promise = fetchProducts();//方式1 正确
promise.then((data) => console.log(data[0].name));
//方式2  错误
console.log(promise[0].name); // “promise”是一个 Promise 对象,因此这句代码无法正常工作
//方式3  正确
console.log(promise[0].name); // “promise”是一个 Promise 对象,因此这句代码无法正常工作

:::tips
注意:
请注意你只能在 async 函数中使用 await,不能在顶层或者同步函数使用,除非代码是 JavaScript 模块。
:::
参考资料:
如何使用 Promise - 学习 Web 开发 | MDN

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 用python创建极坐标平面
  • 住宅代理和数据中心代理:指纹浏览器用哪个更安全?
  • 使用 LinkedList 实现一个高效的缓存系统
  • easyexcel使用教程--导入导出简单案例
  • 第十二章:设置pod和容器权限-保障集群内节点和⽹络安全
  • “微软蓝屏”事件敲响网络安全的警钟
  • C++(2)(数据的共享与保护)
  • Go语言入门
  • Linux安全与高级应用(四)深入探索MySQL数据库:安装、管理与安全实践
  • Journyx项目管理软件 soap_cgi.pyc XXE漏洞复现
  • 【限流与Sentinel超详细分析】
  • 4.8.双向循环神经网络
  • 【C++综合项目】——基于Boost库的搜索引擎(手把手讲解,小白一看就会!!)
  • 前端web开发HTML+CSS3+移动web(0基础,超详细)——第4天
  • priority_queue模拟实现【C++】
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • Hibernate最全面试题
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • JavaScript标准库系列——Math对象和Date对象(二)
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • mockjs让前端开发独立于后端
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • sessionStorage和localStorage
  • spring boot下thymeleaf全局静态变量配置
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 当SetTimeout遇到了字符串
  • 基于游标的分页接口实现
  • 看域名解析域名安全对SEO的影响
  • 配置 PM2 实现代码自动发布
  • 驱动程序原理
  • 深入浏览器事件循环的本质
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 思考 CSS 架构
  • 想写好前端,先练好内功
  • 新版博客前端前瞻
  • 正则表达式
  • 【云吞铺子】性能抖动剖析(二)
  • #、%和$符号在OGNL表达式中经常出现
  • (C#)获取字符编码的类
  • (C语言)字符分类函数
  • (void) (_x == _y)的作用
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (附源码)springboot金融新闻信息服务系统 毕业设计651450
  • (三)Hyperledger Fabric 1.1安装部署-chaincode测试
  • (十七)Flink 容错机制
  • (一)十分简易快速 自己训练样本 opencv级联haar分类器 车牌识别
  • (轉貼) UML中文FAQ (OO) (UML)
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • .NET 5种线程安全集合
  • .NET Core 版本不支持的问题
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .net反编译的九款神器
  • .NET中的Exception处理(C#)