Promises - 从零开始(万字详解)

目录

前言

为何这样设计

回调函数

介绍Promises

使用Promises

创建自己的Promises

Promise 链

传递数据

错误处理Promises

async / await

结语

❤️❤️❤️


前言

在学习JavaScript的过程中往往会面临很多挑战,其中最让人头疼的要数Promises了。想真正理解Promises,需要深入了解JavaScript的工作原理以及它的局限性。如果没有这些背景知识,Promises可能会难以理解,因为如今Promises API已经非常重要,几乎成为了处理异步代码的标准方式。现代的Web API大多是基于Promises构建的。因此,如果想要高效地使用JavaScript,就必须掌握Promises,在本篇文章中,我会从最基础开始,并且会分享一些关键的背景知识,希望在教程结束时,能让你对Promises有一个更加深入的理解最好是可以熟练地运用它们。

目标受众

本篇博文面向初级到中级 JavaScript 开发人员,需要具备一些基本的 JavaScript 语法知识,也可以多多交流。

为何这样设计

 假设我们想建立一个新年快乐!倒计时,如下所示:

function newYearsCountdown() {console.log("3");sleep(1000);console.log("2");sleep(1000);console.log("1");sleep(1000);console.log("新年快乐! 🎉");
}

在这个代码片段中,当程序执行到sleep()调用时会将会暂停,并在指定的时间过后继续执行。不过实际上JavaScript中并没有sleep函数,这是因为JavaScript是一种单线程语言。*所谓“单线程”是指JavaScript只有一个可以执行代码的线程,所以它一次只能做一件事,无法同时处理多项任务。这带来了一个问题:如果JavaScript的线程忙于处理倒计时器,那么它就无法执行其他操作。

当我第一次学习这些概念时,我并没有立刻意识到为什么这是个问题。如果当前唯一正在运行的任务就是倒计时器,那JavaScript线程在这段时间内完全被占用,似乎没什么问题,对吧?

尽管JavaScript没有sleep函数,但它确实有一些其他会占用主线程较长时间的函数。我们可以通过这些方法来窥探一下,如果JavaScript有一个sleep函数会是什么样子,例如,window.prompt()。这个函数用于从用户那里获取信息,它会像上面的sleep()函数那样暂停js代码的执行。

<script>function askForName() {const name = window.prompt('你的名字?');const elem = document.querySelector('#greeting');elem.innerText = `你好 ${name}!`}
</script><button onclick="askForName()">点 击
</button>
<div id="greeting"></div>

点击这个示例中的按钮,然后在提示框弹出时尝试与页面进行交互:

31ca8e81ef9e44319ebe237f061c5df3.png

很明显当提示打开时,页面完全没有响应,无法滚动、单击任何链接或选择任何文本!JavaScript 线程正忙于等待用户输入一个值之后点击确定按钮,以便它可以完成运行后续代码,在等待期间,它无法执行任何其他操作因此浏览器锁定了 UI,其他语言有多个线程,所以如果其中一个线程占用了一段时间也没什么大不了,但在 JavaScript 中,我们只有一个线程,它用于处理页面上的所有事情:处理交互事件、管理网络请求、更新 UI 等。

如果想要创建一个倒计时,需要找到一种不阻塞线程的方法来实现它。

为什么整个浏览器窗口都会冻结?

在上面的例子中,当我们使用window.prompt()时,浏览器在等待我们输入值的过程中,整个用户界面(UI)都会变得无响应。

这看起来有点奇怪……毕竟,浏览器的页面滚动或文本选择并不完全依赖于JavaScript。那么,为什么我们在这段时间内无法进行这些操作呢?

我认为,浏览器之所以设计成这样,是为了防止出现错误。比如,滚动页面时会触发“滚动”事件,这些事件可以被JavaScript捕获并处理。如果在滚动事件发生时JavaScript线程正被其他任务占用,那相关的代码就永远无法执行。如果开发者假设这些滚动事件总是会被处理,那么就有可能导致错误。这也可能是为了用户体验考虑;也许浏览器禁用了UI,以防止用户忽略提示。不过,无论是哪种情况,如果JavaScript中真的有sleep函数,它可能也会以类似的方式工作,以避免潜在的错误。

回调函数

解决这类问题的主要工具是setTimeoutsetTimeout是一个函数,它接受两个参数:

  1. 一段将在未来某个时间点执行的代码。
  2. 等待的时间长度。

以下是一个示例:

console.log('开始');
setTimeout(() => {console.log('已经过去一秒钟');},1000
);

  这段代码是通过一个函数传递进去的,这种模式被称为回调函数。

对于之前的sleep()函数,举个简洁明了的例子 他就像是你给售后打电话一直等待下一个有空的售后客服,而setTimeout()则更像是按下某一个键,让他们售后客服有空时回拨给你。这样你可以挂断电话,继续做其他事情。

setTimeout()被称为异步函数,这意味着它不会阻塞线程。相比之下,window.prompt()是同步的,因为在等待用户输入时,JavaScript线程不能做其他任何事情。

当然如果你使用异步代码的话,它可能导致js代码不会按照线性顺序执行。如下:

console.log('1. setTimeout之前');
setTimeout(() => {console.log('2. etTimeout');
}, 500);
console.log('3. setTimeout之后');

你可能会以为这些日志按顺序从上到下依次输出:1 > 2 > 3。但是,回调函数的本质是其实就是在安排一个延后执行的任务。JavaScript线程不会停下来等待回调,而是继续执行后续的代码。

为了更好地理解这一点,直接看这段代码的运行结果:

  • 00:000: Log "1. Before setTimeout之前".

  • 00:001: 执行timeout.

  • 00:002: Log "3. setTimeout之后".

  • 00:501: Log "2. setTimeout".

 setTimeout()的作用是注册回调函数,注册回调的过程只需要一瞬间,一旦完成,JavaScript就会继续执行程序的其他部分,回调函数在JavaScript中被广泛应用,不仅仅是用于计时器。例如,下面是如何监听指针事件的:

window.addEventListener('pointermove', (event) => {const container = document.querySelector('#data');container.innerText = `${event.clientX} • ${event.clientY}`;
});

window.addEventListener()用于注册一个回调函数,当检测到某个事件时,这个回调函数就会被调用。在这个例子中,监听的是指针的移动,每当用户移动鼠标时,都会触发相应的代码,与setTimeout类似,JavaScript线程并不会专门盯着鼠标事件,而是告诉浏览器当用户移动鼠标的时候,通知一声!当事件触发时,JavaScript线程就会回过头来执行回调函数。不过话说回来,好像我们有点偏离最初的问题了--- 如果我们想设置一个3秒倒计时,该怎么做呢?

在过去,最常见的解决方案是设置嵌套回调,大致像这样:

console.log("3…");
setTimeout(() => {console.log("2…");setTimeout(() => {console.log("1…");setTimeout(() => {console.log("新年快乐!!");}, 1000);}, 1000);
}, 1000);

就上面这一坨代码,不堪入目惨不忍睹!!在setTimeout回调函数内部又创建了自己的setTimeout回调!这种模式的代码,一般只有刚入门的新手敢这么写,虽然可能写这种函数的人也会意识到这种做法并不理想,这种写法就是大家通常说的“回调地狱”。

为了解决这种“回调地狱”的问题,Promises应运而生。
 

setTimeout是怎么知道什么时候触发回调的呢?

setTimeout API接受一个回调函数和一个置顶时间。指定的时间过后,回调函数就会被调用。但它是怎么做到的呢?如果JavaScript线程并没有盯着这个计时器,又是怎么知道该何时调用回调函数的呢?关于这些可以看我之前的一篇博文,叫做事件循环(event loop)的机制。当我们调用setTimeout时,会有一条消息被添加到一个队列中。每当JavaScript线程没有执行代码时,它就会关注事件循环,检查是否有新的消息。如果JavaScript线程此时没有在做其他事情,它会立刻执行setTimeout传递的回调函数。

这也意味着计时器的时间并不是百分之百准确的。JavaScript只有一个线程,如果当消息到达时,它正忙于处理其他任务,比如滚动事件或等待window.prompt()的输入,那么回调的执行可能会稍微延迟。如果我们设置了1000毫秒的计时器,至少会有1000毫秒过去,但实际执行回调的时间可能会稍微长一些。

具体的更加深入的内容可以在CSDN上了解更多关于js事件循环的内容。

介绍Promises

正如前面讨论的那样,我们不能简单地让JavaScript暂停执行代码等待一段时间,因为这样会阻塞线程。我们需要找到一种方法,把工作分解成异步的片段来处理。但是,与其使用嵌套回调,我们能不能将这些任务串联起来呢?就像是告诉JavaScript:“先做这个,再做那个,然后再做这个”?如果想要随意改变setTimeout函数的工作方式该怎么做呢:

console.log('3');
setTimeout(1000).then(() => {console.log('2');return setTimeout(1000);}).then(() => {console.log('1');return setTimeout(1000);}).then(() => {console.log('新年快乐!!');});

 与其直接将回调函数传递给setTimeout,导致嵌套和“回调地狱”的出现,我们能不能使用一种特殊的.then()方法将它们串联起来呢?

这就是Promises的核心理念。Promise是一种特殊的结构,作为JavaScript在2015年一次重大语言更新的一部分被引入,不过setTimeout仍然使用旧的回调方式,因为setTimeout的出现实际上是早于Promises的出现;如果改变它的工作方式,可能会导致一些老的旧的网站出现问题,由此可见向后兼容是非常重要,但这也意味着某些东西会显得有些混乱。不过,现代的Web API大多是基于Promises构建的。

使用Promises

fetch()函数是JavaScript中用于发起网络请求的API,通常用来从服务器获取资源或发送数据。

看看下面这段代码:

const fetchValue = fetch('/api/get-data');
console.log(fetchValue);
// -> Promise {<pending>}

当调用fetch()时,它开始发起网络请求。这是一个异步操作,所以JavaScript线程不会停下来等待,而是继续执行后续的代码。那么,fetch()函数实际上产生了什么呢?它不可能直接返回来自服务器的实际数据,因为请求刚刚开始,需要一段时间才能完成。相反,fetch()返回的是一种“承诺”(Promise),就像浏览器给你的一张IOU(欠条),意思是:“我现在还没有数据,但我保证很快会有!”更具体地说,Promise是JavaScript的一个对象。Promise内部始终处于以下三种状态之一:

  • pending(待定):工作正在进行中,还未完成。
  • fulfilled(已完成):工作已经成功完成。
  • rejected(已拒绝):出现了问题,Promise未能完成。

当Promise处于pending状态时,称它为“未完成”(unresolved)。当工作完成时,Promise变为“已完成”(resolved),无论是成功完成(fulfilled)还是遇到问题(rejected),都属于“已完成”状态,实际情况中通常都希望在Promise完成时执行某些操作,这里可以通过.then()方法来实现这一点:

fetch('/api/get-data').then((response) => {console.log(response);// Response { type: 'basic', status: 200, ...}});

 fetch()返回一个Promise,使用.then()方法来附加一个回调函数。当浏览器收到响应后,这个回调函数就会被调用,并且响应对象会作为参数传递给回调函数。

等待JSON

如果你之前使用过Fetch API,你可能注意到在获取需要的JSON数据时,还需要执行第二步操作:

fetch('/api/get-data').then((response) => {return response.json();}).then((json) => {console.log(json);// { data: { ... } }});

response.json()会生成一个全新的Promise,这个Promise会在响应数据完全转化为JSON格式时转为完成态fulfilled。但为什么response.json()是异步的呢?我们已经等到响应了,数据不是应该已经是JSON格式的吗?其实不一定,Web底层的一个核心功能是服务器可例如视频),但也可以用于传输较大的JSON数据,当浏览器收到服务器传来的第一个字节数据时,fetch()返回的Promise就转为完成态fulfilled。而response.json()返回的Promise是在浏览器接收到所有数据后才会转为完成态fulfilled。在实际应用中,JSON数据通常不会分块发送,因此这两个Promise通常会在同一时间被解决。不过,为了支持流式响应,Fetch API被设计成这种结构,因此需要多一步的处理来确保数据完整性。

创建自己的Promises

当使用Fetch API时,Promise是由fetch()函数在后台创建的。但如果使用的API不支持Promises怎么办?

例如,setTimeout是在Promises出现之前创建的。如果我们想在使用setTimeout时避免“回调地狱”,我们需要自己创建Promises。以下是创建Promise的语法示例:

const demoPromise = new Promise((resolve) => {//添加些异步操作 then//调用 `resolve()` 
});
demoPromise.then(() => {// promise执行完成时调用此处
})

Promises是通用的,它们本身不“做”任何事情。当使用new Promise()创建一个新的Promise实例时,需要提供一个函数,用于执行想要的特定异步工作。这个工作可以是任何事情:发起网络请求、设置延迟等等,当这个工作完成时调用resolve(),这会通知Promise一切顺利,并将其状态设置为已解决(resolved)。

话再说回来创建一个倒计时计时器。

在这种情况下,异步工作就是等待setTimeout的到期,可以创建一个基于Promise的辅助函数,将setTimeout包裹起来,代码如下:

function wait(duration) {return new Promise((resolve) => {setTimeout(resolve, duration);});
}
const timeoutPromise = wait(1000);
timeoutPromise.then(() => {console.log('1秒之后!')
});

这段代码看起来挺复杂,接下来试着把它拆解一下:

  1. 新建的工具函数 wait:这个函数接收一个参数 duration,希望将其用作类似于“休眠”的功能,不过这里它是完全异步的。

  2. wait 函数内部,创建并返回了一个新的Promise。需要注意的是,Promise本身不会主动做任何事情;我们需要在异步工作完成后调用resolve函数。

  3. 在Promise内部,启动了一个新的定时器,使用setTimeout。将从Promise中获取的resolve函数以及用户提供的duration传递给它。

  4. 当定时器到期时,它会调用传入的回调函数。这就像是一种连锁反应:setTimeout调用resolve,这表明Promise已经完成,然后触发.then()中的回调函数。

即使这段代码让你感觉有点晕😅也没关系。这里结合了很多复杂的概念!希望你能够理解大致内容~~~

在上面的代码中,我将resolve函数直接传递给了setTimeout。或者也可以像之前那样一层一层来调用resolve函数:

function wait(duration) {return new Promise((resolve) => {setTimeout(() => resolve(),duration);});
}

 实际上JavaScript 中有“头等函数”(first-class functions),是指在程序设计语言中,函数被当作头等公民。这意味着,函数可以作为别的函数的参数、函数的返回值,赋值给变量或存储在数据结构中。这是一个很棒的特性,但要真正熟练运用,可能需要一些时间让它变得直观。上述的替代写法虽然间接一些,但功能完全相同。所以,如果这种方式对你来说更清晰,你完全可以这样结构化代码!

Promise 链

关于 Promise,有一件重要的事情需要理解:它只能被解决(resolved)一次。一旦一个 Promise 被完成(fulfilled)或被拒绝(rejected),它的状态就会永久保持不变,这意味着 Promise 并不是万能的,也有不适合的某些场景,比如事件监听器:

window.addEventListener('mousemove', (event) => {console.log(event.clientX);
})

这个回调函数会在用户每次移动鼠标时触发,可能会触发数百次甚至数千次。对于这种情况,Promise 并不是一个好的选择。那么,对于之前提到的“倒计时”场景呢?虽然不能重新触发同一个 wait Promise,但可以将多个 Promise 链接在一起:

wait(1000).then(() => {console.log('2');return wait(1000);}).then(() => {console.log('1');return wait(1000);}).then(() => {console.log('新年快乐!!');});

当我们的原始 Promise 被完成时,.then() 回调函数会被调用,这个回调创建并返回一个新的 Promise,整个过程就会重复进行。

传递数据

说了这么多,到目前为止,在调用 resolve 函数时并未传递任何参数,只是用它来表示异步工作已经完成而已,但在某些情况下,可能有一些数据需要传递给下一个处理步骤!下面是一个使用回调的例子:

function getUser(userId) {return new Promise((resolve) => {// 这里的异步操作是根据用户ID查找用户信息db.get({ id: userId }, (user) => {// 一旦获取到完整的用户对象,就调用 resolve 并传递用户数据resolve(user);});});
}
getUser('abc123').then((user) => {// 在这里我们可以处理获取到的用户对象console.log(user);// 输出类似于 { name: 'Josh', ... } 的用户对象
})

在这个例子中,定义了一个 getUser 函数,它接受一个 userId 参数,并返回一个 Promise。当调用 getUser('abc123') 时,返回的 Promise 会在用户数据准备好后被解析,然后在 .then() 回调中接收到用户对象并处理它,通过这种方式,我们不仅可以使用 resolve 函数通知 Promise 已经完成,还可以将数据传递给下一个处理步骤,使整个异步操作链更加灵活和强大。

错误处理Promises

在 JavaScript 中,Promises 并不总是能准确无误的完成,有时候,它们可能会被“打破”。例如,在使用 Fetch API 时,我们不能保证网络请求一定会成功!可能是网络连接不稳定,或者服务器出现故障。在这些情况下,Promise 会被拒绝(rejected),而不是完成(fulfilled),这种情况可以使用 .catch() 方法来处理这些被拒绝的 Promises:

fetch('/api/get-data').then((response) => {// ...}).catch((error) => {console.error(error);});

当一个 Promise 被完成时,.then() 方法会被调用,而当 Promise 被拒绝时,.catch() 方法会被调用,可以把它看作是两条独立的路径,根据 Promise 的状态来选择哪条路径被执行。

Fetch 例外情况

假设服务器返回了一个错误,比如 404 Not Found 或 500 Internal Server Error。这会导致 Promise 被拒绝吗?

并不会。在这种情况下,Promise 仍然会被认为是“完成”的,而 Response 对象会包含关于错误的信息: 

Response { ok: false, status: 404, statusText: 'Not Found', }

虽然有点意外,但实际上是有道理的:成功地接收到了来自服务器的响应!虽然这个响应可能不是我们想要的,但我们确实得到了一个响应!!

这在某种程度上符合了Promise的逻辑:已经得到了响应,即使它不是期望的那种数据。

在手动创建 Promises 时,可以通过传递第二个回调参数 reject 来拒绝 Promise。这个 reject 回调函数会被调用,以表示 Promise 由于某种原因未能成功完成。 

new Promise((resolve, reject) => {someAsynchronousWork((result, error) => {if (error) {reject(error);return;}resolve(result);});
});

如果在 Promise 内部遇到问题,可以调用 reject() 函数将 Promise 标记为拒绝状态。传递给 reject() 的参数(通常是一个错误)将会传递到 .catch() 回调中被捕获。

正如之前所看到的,Promises 始终处于三种可能的状态之一:pending(等待中)、fulfilled(已完成)、rejected(已拒绝)。然而,Promise 是否被“解决”(resolved)是一个独立的概念。那么,参数名称不应该是 “fulfill” 和 “reject” 吗?

实际上,resolve() 函数通常会将 Promise 标记为已完成,但这并不是绝对的保证,如果用另一个 Promise 来解决原始的 Promise,情况会变得比较复杂。原始 Promise 会“锁定”到这个后续 Promise 上。即使原始 Promise 仍然处于等待状态,它也会被认为是“已解决”的,因为 JavaScript 线程已经转到下一个 Promise 上。

这是我在发布这篇博客文章时刚刚学到的,不过我认为99% 的人都不需要担心这件事情。如果你确实想深入了解,可以查阅这个文档:States and Fates。

async / await

新的JavaScript 有一个很棒的特性是 async / await 语法,使用这种语法可以实现接近理想的倒计时结构:

async function countdown() {console.log("5…");await wait(1000);console.log("4…");await wait(1000);console.log("3…");await wait(1000);console.log("2…");await wait(1000);console.log("1…");await wait(1000);console.log("新年快乐!");
}

but!!!!等一下,不是说过这种做法是不可能的!不能在 JavaScript 函数执行到一半时暂停,这不是会阻塞线程,使其无法执行其他任务吗?

 实际上,这种新语法背后是基于 Promises 的。如果仔细研究一下,就会发现它是如何工作的:

async function addNums(a, b) {return a + b;
}
const result = addNums(1, 1);
console.log(result);
// -> Promise {<fulfilled>: 2}

上面代码期望返回值是数字 2,但实际上返回的是一个解析为 2 的 Promise。当在一个函数前加上 async 关键字时,这个函数就一定会返回一个 Promise,即使这个函数内部没有任何异步操作!!实际上等同于:

function addNums(a, b) {return new Promise((resolve) => {resolve(a + b);});
}

同样,await 关键字可以算是 .then() 回调的语法糖:

// 这段代码...
async function pingEndpoint(endpoint) {const response = await fetch(endpoint);return response.status;
}
// ...等同于这段代码:
function pingEndpoint(endpoint) {return fetch(endpoint).then((response) => {return response.status;});
}

Promises 使得 JavaScript能够提供看起来像是同步的语法,但实际上在内部依然是异步的~~~不过也确实是挺厉害的!!

结语

懒得写了,都看到这里了的话,估计你眼睛也累了,休息休息吧!码字不易别忘了点赞哦。

❤️❤️❤️

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1522524.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【CF补题数学裴蜀定理】 div969 C Dora and C++

Dora and C 分析&#xff1a; 对于两个数x,y 我们想要通过如下操作使得他们的差变得尽可能小 我们要如何操作&#xff1f; 这个操作也就是相当于 d e l ∣ y − x ∣ − k 1 ∗ x − k 2 ∗ y del|y-x|-k_1*x-k_2*y del∣y−x∣−k1​∗x−k2​∗y&#xff0c;让这个差值最小…

【网络安全】IDOR之敏感数据泄露

未经许可,不得转载。 文章目录 正文正文 在测试“添加到收藏夹”功能时,我拦截了发送到服务器的请求,请求体如下: {“uriTemplate”:“asset/{assetId}/favorite”,“version”:“v2”,“type”:“POST”,“req_service”:“pict”,“url”:“asset/VICTIMS_ASS…

Android Google Maps

Android 谷歌地图 前言正文一、设置Google Cloud 项目二、项目配置① 设置SDK② 配置API密钥③ 配置AndroidManifest.xml 三、添加地图四、定位当前① 请求定位权限② 我的位置控件③ 获取当前位置 五、配置地图① xml配置地图② 代码配置地图③ 地图点击事件④ 管理Marker 六、…

薛定谔的空气墙?一文带你了解其背后的技术原理

封面图 悟空来了都得撞墙&#xff1f; 目前&#xff0c;被称作“村里第一个大学生”的国产3A游戏《黑神话&#xff1a;悟空》发售已经有一段时间了&#xff0c;游戏采用虚幻引擎4技术&#xff0c;仿佛将传统与现代的界限模糊&#xff0c;玩家游玩时沉浸感极强。然而&#xff…

自然语言处理系列五十三》文本聚类算法》文本聚类介绍及相关算法

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《自然语言处理原理与实战》&#xff08;人工智能科学与技术丛书&#xff09;【陈敬雷编著】【清华大学出版社】 文章目录 自然语言处理系列五十三文本聚类算法》文本聚类介绍及相关算法K…

四、基本电路设计笔记——4.1 DC-DC稳压电路

目录 4.1 DC-DC稳压电路 4.1.1 基于MT2492的DC-DC稳压电路 &#xff08;1&#xff09;芯片参数 &#xff08;2&#xff09;芯片引脚 &#xff08;3&#xff09;输出电压设置 4.1.2 基于MT2499A的DC-DC稳压电路 &#xff08;1&#xff09;芯片参数 &#xff08;2&#xf…

多模态生成发文量大涨!最新成果统一Transformer和Diffusion,含金量超高

最近多模态生成领域也在“神仙打架”&#xff0c;比如Meta的全新训练方法Transfusion&#xff0c;用单个模型就能同时生成文本和图像&#xff01; 还有之前华为、清华提出的个性化多模态内容生成技术PMG&#xff0c;生成的内容可“量身定制”&#xff0c;更能满足偏好。 这些…

深入解析Linux轻量级进程:线程的概念、原理、优缺点及其与进程的关系与区别

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 &#x1f4da;Linux线程&#x1f4d5;什么是线程*可以使用多进程去并发的执行一个进程的代码&#xff0c;那为什么要由线程呢&#x…

搭子小程序开发,让社交更加有趣

如今&#xff0c;搭子成为了年轻人社交的新兴方式&#xff0c;它作为一种连接年轻人的社交纽带&#xff0c;深受大众的欢迎&#xff01;各式各样的旅游搭子、健身搭子、游戏搭子等&#xff0c;让年轻人享受到社交的魅力。 随着互联网的发展&#xff0c;寻找搭子也发展到了线上…

一个好用的Maven依赖冲突解决插件:Maven Helper

在项目开发&#xff0c;或项目Maven需要新增依赖、项目依赖组件升级时&#xff0c;经常会出现添加后&#xff0c;因为各个模块中有相同的依赖、不同的版本而导致依赖冲突&#xff0c;从而导致项目启动不起来&#xff0c;这种冲突非常恶心&#xff0c;因为是传递依赖所以会看不出…

Hackme靶场渗透攻略

步骤一&#xff0c;注册登录进去 步骤二&#xff0c;点击search 我们发现有很多书 步骤三&#xff0c;搜索一本书抓包发放到重放器 步骤四&#xff0c;数据改为1*&#xff0c;复制数据包到1.txt&#xff0c;然后打开sqlmap 步骤五&#xff0c;sqlmap查看当前数据库 python s…

多模态AI:原理、应用与未来展望

随着人工智能技术的飞速发展&#xff0c;多模态AI逐渐成为构建智能系统的重要方向。传统的AI系统通常依赖于单一模态的数据&#xff0c;如文本、图像或音频。而多模态AI通过结合多种数据类型&#xff0c;能够在更复杂的场景下提供更智能的解决方案。本文将深入探讨多模态AI的原…

Android 11 (R)AMS Activity内部机制

一、AMS是如何被管理的 如我们在Android 11(R)启动流程中介绍的一样&#xff0c;AMS和ATMS是在SystemServer中被启动的 ActivityTaskManagerService atm mSystemServiceManager.startService(ActivityTaskManagerService.Lifecycle.class).getService(); mActivityManagerSe…

使用vscode debug cpp/python混合编程的程序(从python调用的C++编译的dll)

使用vscode debug cpp/python混合编程的程序&#xff08;从python调用的C编译的dll&#xff09; 1. 安装插件 Python C Debugger https://marketplace.visualstudio.com/items?itemNamebenjamin-simmonds.pythoncpp-debug 2. 在.vscode/launch.json中增加配置 拷贝自 https:…

默默的学python——两个重要的函数dir()、help()

一、dir()函数 dir()函数在Python中用于返回一个对象的所有属性和方法的列表&#xff0c;当你对一个函数使用dir()时&#xff0c;它会返回函数对象的所有可访问的属性和方法的名字列表。 具体的说&#xff0c;dir()函数获取的内容包括&#xff1a; 1.特殊方法和魔法方法 如…

Kettle 锁表原因及解决办法【源码级分析】

文章目录 背景源码分析锁表场景1:资源库锁表锁表场景2:写日志锁表在哪里配置的kettle_log_table?官方解释自增 SQL 获取 BatchI 原理解决自增 SQL 获取 BatchID背景 Kettle 7.1.0 经常出现锁表的情况,体现为在数据库里有一条锁表 SQL,然后整个 Kettle 都无法运行。😂�…

App推广新姿势:Xinstall一键下载唤起,轻松提升用户体验!

在App推广和运营的道路上&#xff0c;你是否遇到过这样的困扰&#xff1a;用户点击下载链接后&#xff0c;却无法直接唤起App&#xff0c;导致用户体验不佳&#xff0c;甚至造成用户流失&#xff1f;别担心&#xff0c;今天我们就来科普一个神器——Xinstall&#xff0c;它能帮…

【GIT】idea中实用的git操作,撤回commit,撤回push、暂存区使用

IDEA中最常见的UI操作&#xff1a;【GIT】Idea中的git命令使用-全网最新详细&#xff08;包括现象含义&#xff09; 文章目录 问题一&#xff1a; idea撤回仅commit错误的代码&#xff08;仅本地仓库&#xff0c;因为还没推送到远程&#xff09;问题二&#xff1a; idea撤回Com…

8个优质视频素材库,商用无忧

如果你正在寻找一些优质的视频素材库&#xff0c;不妨看看以下这些网站。它们提供了各种各样的视频素材&#xff0c;无论是用于家庭视频制作、Vlog、还是社交媒体内容&#xff0c;都能找到合适的素材。从生活日常到创意动画&#xff0c;这些网站都能帮你找到想要的视频素材。一…

学习react day01

&#xff08;1&#xff09;nodejs.cn 中文网 版本须较新 &#xff08;2&#xff09;全局安装 npm install create-react-app -g &#xff08; 版本查询 create-react-app -V&#xff09; &#xff08;3&#xff09;创建app create-react-app test-app &#xff08;4&…