【超级 ping 前端源码】【小龟网源码】【竞异动排序源码】promise源码详解

时间:2024-12-24 08:18:42 编辑:原神1.5 源码 来源:源码人才

1.从零开始手写Promise
2.Axios源码深度剖析 - AJAX新王者
3.深入p-limit源码,码详如何限制并发数?
4.从规范面解读:Promises/A+规范与浏览器Promise规范有何区别?
5.Promise原理详解(二)
6.c++基础语法之future,码详promise,码详async详细讲解-SurfaceFlinger学习必备c++知识

promise源码详解

从零开始手写Promise

       面试时,码详常被问及Promise应用;深入者或询问其实现细节,码详或查阅源码。码详超级 ping 前端源码本文聚焦于探究Promise内部如何实现链式调用。码详

       所谓Promise,码详实质是码详一个容器,存储异步操作的码详结果。其提供统一接口,码详便于处理各种异步操作。码详

       在Promise出现前,码详异步操作常通过回调函数实现,码详但过度嵌套引发回调地狱。码详Promise解决此痛点,简化回调复杂性。

       Promise/A+规范,由社区提出,为业内所接受。规范定义Promise行为,包括状态转换不可逆,终值不可改变。

       实现Promise需构造函数实例化对象,通过实例的then方法处理异步结果。规范要求Promise必须包含等待态、执行态和拒绝态。

       Promise构造函数立即执行,传入resolve和reject函数。异常情况通过try/catch捕获处理。

       Promise状态一旦改变,无论成功或失败,都会触发then回调函数。回调函数需根据状态调用对应处理终值的函数。

       规范允许onFulfilled和onRejected参数可选。实现时,对参数进行类型判断,忽略非函数参数。

       通过一个四十行左右的简单Promise垫片,我们初步实现Promise基本结构与then方法。

       链式调用是Promise核心。规范要求每个then方法返回新Promise对象,允许方法连续调用。

       实现链式调用需返回新的Promise对象,避免调用时覆盖或丢失回调函数。通过返回Promise解决。

       规范中,then方法返回Promise对象后,处理onFulfilled和onRejected时,需考虑值的传递特性。

       Promise解决过程抽象,需输入Promise和值x。x为thenable对象时,接受x状态;否则使用x值执行。小龟网源码

       实现解决过程时,首先排除传入参数自身情况,之后判断x是否为对象或函数,取then方法处理。

       取then方法时,需使用try/catch捕获可能出现的错误,防止恶意代码导致程序崩溃。

       对不同情况正确判断,处理函数调用,以及递归处理嵌套Promise,实现完整链式调用。

       验证通过promises-aplus-tests工具,确保实现符合规范。

       了解更多前端知识,请关注公众号前端壹读。如认可内容,欢迎关注专栏或访问网站获取更多文章。

Axios源码深度剖析 - AJAX新王者

       Axios 是一个基于 Promise 的 HTTP 请求库,支持浏览器和 Node.js 环境。其源码在 GitHub 上开源,欢迎 fork 使用并提出指正。以下为 Axios 的核心目录结构说明,主要关注在 /lib/ 目录下的文件。

       在使用 Axios 时,你可能会遇到多种调用方式,本文将带你深入了解这些方式及其原理。

       首先,我们来了解一下 Axios 的基本用法。你可以使用以下几种方式发起请求:

       1. `axios(option)`:提供一个配置对象进行调用。

       2. `axios(url[, option])`:传入 URL 和配置对象。

       3. 对于 GET、DELETE 等方法:`axios[method](url[, option])`。

       4. 对于 POST、PUT 等方法:`axios[method](url[, data[, option]])`。

       5. 使用默认实例:`axios.request(option)`。

       通过以上方式,你可以轻松发起 HTTP 请求。

       深入源码分析,你将发现 Axios 的强大之处。通过 `axios.js` 文件的入口,核心在于 `createInstance` 方法,该方法能生成一个指向 `Axios.prototype.request` 的 Function,从而实现多种调用方式。

       在 Axios 的核心 `Axios` 类中,`request` 方法是所有功能的中枢,无论是 GET、POST 还是其他方法,最终都通过 `request` 方法实现。

       配置项是 Axios 与用户交互的关键,它涵盖了几乎所有功能的配置。配置项从低到高优先级顺序为:默认配置对象、`defaults` 属性、`request` 方法参数。竞异动排序源码

       在使用 Axios 时,配置项是如何生效的?答案在于合并多个配置源,最终得到一个综合配置对象。

       此外,Axios 提供了拦截器系统,让你可以控制请求前后的数据处理。每个 Axios 实例都有 `interceptors` 属性,用于管理拦截器,让你实现精细的控制。

       核心的 `dispatchRequest` 方法则负责处理请求流程,包括请求适配器、发送请求、数据转换等步骤。最后,通过 Promise,你可以优雅地处理异步请求。

       数据转换器让你能轻松地在请求和响应数据之间进行转换,如将对象转换为 JSON 格式。默认情况下,Axios 自动处理 JSON 数据转换。

       在使用 Axios 时,你还能灵活地控制超时、取消请求、设置 header、携带 cookie 等功能。通过源码分析,你可以深入理解 Axios 的内部机制。

       总结,Axios 以其强大、灵活的功能和简洁的 API 设计,成为现代应用中不可或缺的 HTTP 请求工具。通过本文的深入探讨,你将对 Axios 的运作机制有更深刻的理解,从而更好地利用其功能。

深入p-limit源码,如何限制并发数?

       并发处理在现代编程中扮演着至关重要的角色,尤其在异步操作和并行任务处理中。虽然JavaScript是单线程执行的,但它通过Promise.all等API实现了并发效果,允许同时处理多个异步操作。

       Promise.all是Promise库中的一个关键函数,它接受一个Promise数组作为参数。此函数会等待所有给定的Promise实例全部完成或其中一个失败,然后返回一个新Promise的数组结果。如果所有Promise都成功,则返回所有成功结果的数组;如果一个或多个Promise被拒绝,则返回第一个拒绝的Promise的reason。

       然而,有时并发操作需要被限制。过多的并发请求可能给服务器带来压力,影响性能。这时候,p-limit库就显得尤为重要,arduino遥控开关源码它允许我们为并发操作设置一个上限。

       p-limit提供了pLimit函数来定义并发限制。使用pLimit时,你可以传入一个数量参数,这个参数决定了同时可以执行的异步任务数量。函数返回一个新函数,该函数接收需要并发执行的异步任务。当执行队列中的任务数量达到上限时,新传入的任务会被加入队列,等待前面的任务释放资源后执行。

       p-limit的实现中,核心在于初始化一个计数器和一个任务队列。队列采用了yocto-queue库实现,它提供了一个基于链表的队列结构。在并发处理过程中,p-limit通过enqueue函数将异步任务入队,并在队列中管理任务的执行顺序和限制。

       enqueue函数负责将异步任务入队,同时对任务进行包装和控制,确保任务在队列中按顺序执行,且不会超过指定的并发限制。这通过使用async函数实现,以确保等待下一个微任务的到来,从而在异步更新的activeCount值上进行比较,以维持并发限制。

       在实际执行时,每个任务的执行由run函数控制。此函数在内部管理并发计数,并在任务完成后执行下一个任务,确保并发限制被严格遵守。enqueue、run和next三个函数协同工作,构成了p-limit中一个动态、有限的异步任务执行流程。

       此外,p-limit还包含了辅助函数用于管理任务状态,如获取当前执行任务数量(activeCount)、队列中等待任务数量(pendingCount)以及清空任务队列(clearQueue)。这些功能共同协作,确保并发处理既高效又可控。

       通过p-limit库,开发人员能够轻松实现异步操作的并发控制,优化性能并防止服务器过载。了解其内部机制,能更好地利用并发处理技术,提升应用响应速度和用户体验。

从规范面解读:Promises/A+规范与浏览器Promise规范有何区别?

       前言

       Promise是一种优秀的异步解决方案,其原生实现更是面试中的爆点,提到Promise实现,我们首先会想起Promises/A+规范,大多数教程中都是按照Promises/A+规范来实现Promise。

       小包也是Promises/A+圣经的执行者之一,但小包心中一直有个好奇,代付过0源码遵循Promises/A+规范实现的Promise与ES6-Promise能有什么区别呐?

       文章中的测试代码选取小包基于Promises/A+规范实现的原生Promise

       学习本文,你能收获:

       进一步完善原生Promise的实现

       更进一步理解Promise与microTask之间的关系

promise的成功值value

       Promises/A+规范只提供了value的定义,并没有详细说明如何处理不同类型的value值:

       “value”isanylegalJavaScriptvalue(including?undefined,athenable,orapromise).value可以是任意合法的JavaScript值,包括undefined、具备then接口的对象或者promise

       但ECMAScript规范对不同类型的value做了细致的处理。

       红框部分我们可以看出,ES6规范会根据resolution(相当于Promises/A+规范中的value)类型选取不同的执行方案。

       判断resolution是否为Object,如果不是,直接执行FulfillPromise

       如果是Object,试探是否存在then接口

       判断then是否可执行(abruptcompletion可以理解为非正常值)

       如果then可执行,将then方法放入事件队列中。

       PromiseResolveThenableJob:该job使用传入的thenable的then方法来解决promise。

       一句话总结上面的过程:如果value值为可thenable对象或者promise,ES6会采用该thenable的状态。

       小包举个栗子:

const?p?=?new?Promise((resolve)?=>?{ ?resolve(1);});const?p1?=?new?Promise((resolve)?=>?{ ?resolve(p);});p1.then((d)?=>?console.log(d));

       p1接收的成功值value为Promisep,p状态为fulfilled,这种情况下ES6中会采取p的状态及value,因此最终打印1。

       我们将p更换为具备thenable对象,结果也是类似的。

//?类?promise?对象const?p1?=?{ ?a:?1,?then(onFulfilled,?onReject)?{ onFulfilled(this.a);?},};const?p2?=?new?Promise((resolve)?=>?{ ?resolve(p1);});//?1p2.then((d)?=>?console.log(d));

       Promises/A+没有对此进行规范,因此当传入的value为thenable对象时,会原封不动的输出。

       那我们应该如何完善这部分代码呐?我们需要对value值进行解析,如果value可thenable,则采纳他的状态和值,递归进行上述步骤,直至value不可thenable。(这里与resolvePromise部分递归解析onFulfilled函数的返回值是类似的)

const?resolve?=?(value)?=>?{ ?if?(typeof?value?===?"object"?&&?value?!=?null)?{ try?{ ?const?then?=?value.then;?if?(typeof?then?===?"function")?{ return?then.call(value,?resolve,?reject);?}}?catch?(e)?{ ?return?reject(e);}?}?if?(this.status?===?PENDING)?{ this.value?=?value;this.status?=?FULFILLED;this.onFulfilledCallbacks.forEach((cb)?=>?cb(this.value));?}};Promise与microTask

       Promises/A+规范中其实并没有将Promise对象与microTask挂钩,规范是这么说的:

       Here“platformcode”meansengine,environment,andpromiseimplementationcode.Inpractice,thisrequirementensuresthat?onFulfilled?and?onRejected?executeasynchronously,aftertheeventloopturninwhich?then?iscalled,andwithafreshstack.Thiscanbeimplementedwitheithera“macro-task”mechanismsuchas?setTimeout?or?setImmediate,orwitha“micro-task”mechanismsuchas?MutationObserver?or?process.nextTick.Sincethepromiseimplementationisconsideredplatformcode,itmayitselfcontainatask-schedulingqueueor“trampoline”inwhichthehandlersarecalled.

       Promises/A+规范中表示then方法可以通过setTimeout或setImediate等宏任务机制实现,也可以通过MutationObserver或process.nextTick等微任务机制实现。

       但经过大量面试题洗礼的我们知道浏览器中的Promise.then典型的微任务。既然都学到这里了,小包索性就打破砂锅问到底,找到Promise与microTask挂钩的根源。

谁规定了Promise是microTask

       标准读起来属实有些无聊,但好在小包找到了最终的答案。

       首先小包先入为主的以为,Promise的详细规定应该都位于ECMAScript制定的规范中,但当小包进入标准后,全局搜索micro,竟然只搜索到三个Microsoft。讲实话,小包是震惊的,ECMAScript并没有规定Promise是microTask。

       ECMAScript规范中,最接近的是下面两段表达:

       The?host-defined?abstractoperationHostEnqueuePromiseJobtakesarguments?job?(a?Job?AbstractClosure)and?realm?(a?RealmRecord?or?null)andreturns?unused.Itschedules?job?tobeperformedatsomefuturetime.The?AbstractClosures?usedwiththisalgorithmareintendedtoberelatedtothehandlingofPromises,orotherwise,tobescheduledwithequalprioritytoPromisehandlingoperations.

       JobsarescheduledforexecutionbyECMAScripthostenvironments.ThisspecificationdescribesthehosthookHostEnqueuePromiseJobtoscheduleonekindofjob;hostsmaydefineadditionalabstractoperationswhichschedulejobs.SuchoperationsacceptaJobAbstractClosureastheparameterandscheduleittobeperformedatsomefuturetime.Theirimplementationsmustconformtothefollowingrequirements:

       上面两句话意思大约是:ECMAScript中将Promise看作一个job(作业),HostEnqueuePromiseJob是用来调度Promise作业的方法,这个方法会在未来某个时间段执行,具体执行与Promise的处理函数或者与Promise处理操作相同的优先级有关。

       那何处将Promise规定为microTask呐?---HTML标准

       HTML标准中指出:

       JavaScriptcontainsan?implementation-defined?HostEnqueuePromiseJob(job,?realm)abstractoperationtoschedulePromise-relatedoperations.HTMLschedulestheseoperationsinthemicrotaskqueue.

       上述标准的最后一句话指出,HTML将在microqueue中安排这些操作。破案了,原来是HTML标准中将Promise规定为microTask。(为什么会是HTML进行规定,小包还没有探究出来)

       更深入的区别,请参考月夕大佬:V8Promise源码全面解读

后语

       我是?战场小包?,一个快速成长中的小前端,希望可以和大家一起进步。

       如果喜欢小包,可以在?掘金?关注我,同样也可以关注我的小小公众号——小包学前端。

       一路加油,冲向未来!!!

疫情早日结束人间恢复太平

       原文:/post/

Promise原理详解(二)

       在深入探讨Promise原理的第二部分中,我们继续从源码角度分析Promise的实现与使用。在上一节中,我们已了解了如何创建并赋值给Promise对象,以及在特定上下文下如何进行传递和调用。

       Promise的核心方法之一便是`then`,它定义了访问Promise当前值、最终值及异常处理的机制。`then`方法可以接受两个参数,即成功回调函数`onFulfilled`和错误回调函数`onRejected`,这两个函数是可选的。当参数不是函数时,会发生值穿透。重要的是,`then`方法只能调用一次,但可以多次调用`then`以实现链式调用。

       需要注意的是,`then`方法的返回结果必须是Promise对象。这意味着在调用`then`之后,返回的Promise对象将继承上一个`then`调用的返回值作为其参数传递给下一个`then`方法。

       以示例代码为例,第二个`then`方法将利用上一个`then`调用返回的值。

       接下来,我们将聚焦于`then`函数的内部实现,以深入理解其如何满足上述规范。

       `then`函数首先获取当前Promise对象,并创建一个名为`child`的Promise对象,然后返回这个`child`对象,确保遵循`then`方法必须返回Promise对象的规则。

       接下来的步骤涉及一系列判断和操作,这些操作主要围绕于确保Promise的生命周期和状态转换的正确性。首先,函数对`child`进行判断,若其未被初始化,便调用`makePromise`函数进行初始化。此步骤确保Promise对象的完整性。

       接着,获取当前`then`方法所访问的Promise对象的`_state`属性。这个属性反映了Promise的状态:执行、拒绝或等待。基于此状态,`then`方法执行不同的逻辑操作。

       当Promise处于执行或拒绝状态时,`then`方法会调用`invokeCallback`函数,执行相应的回调。而当Promise处于等待状态时,会调用`subscribe`函数,将回调函数添加至事件队列,等待Promise状态转换。

       以代码为例,当存在一个`setTimeout`函数并延迟毫秒时,Promise的状态为等待状态。此时,`subscribe`函数被调用,将回调函数添加至事件队列,等待`setTimeout`触发。

       `subscribe`接收四个参数:父级Promise对象、当前`then`方法返回的`child`对象、成功回调函数和拒绝回调函数。获取父级Promise的事件队列,并在队列尾部添加事件,确保回调函数的正确执行。

       之后,检查事件队列的长度和父级Promise的状态,若非等待状态,则执行`publish`函数,将父级Promise作为参数传递给`publish`函数。至此,Promise的事件队列准备就绪,静待`resolve`或`reject`函数的触发。

       在当前阶段,若`setTimeout`函数未返回值,事件队列已准备,静待Promise对象调用`resolve`或`reject`函数。这一阶段的流程梳理至此结束。

       接下来,我们深入探讨`resolve`方法及其执行流程。假设`setTimeout`函数已触发。在深入分析之前,我们先回顾之前的方法调用流程,并在代码中找出关键点。接下来的分析将集中在`resolve`方法的具体实现及其对事件队列的处理过程。

       当`resolve`函数被调用时,若其参数值是字符串类型,将直接进入`fulfill`函数。在`_subscribers`数组中,长度通常为3,因为已添加回调函数,`_subscribers`数组内容为`[child, callback(), null]`。

       接下来,执行`publish`函数,获取事件队列。这里巧妙之处在于,通过`_state`属性来定位执行回调函数,`FULFILL`状态对应`1`,`REJECTED`状态对应`2`,以此精确确定执行的回调函数。

       具体操作如下:第一个参数表示状态,第二个参数是Promise对象,第三个参数是回调函数,第四个参数是回调函数的参数。这里的`res`相当于第四个参数。

       首先,判断回调函数是否为函数。如果不是,将值赋给`detail`,实现值穿透。如果回调函数是函数,则通过`try-catch`捕获异常,若捕获到异常,则将`error`赋值,否则设置`successed`为真值。确保`value`不等于Promise对象本身,避免递归死循环。此时,第一个`then`函数的执行完毕。

       在上述分析的基础上,根据规范指导执行相应的操作。具体细节可参考相关文档。

       特别提到的是,`resolve`方法的递归调用及其对事件队列的清空过程。在特定情况下,若`value`是对象或函数,处理方式与前述情况类似。然而,第二种情况的详细解释将留待下一节深入探讨。

       在总结部分,我们简要介绍了`then`方法的其他分支,即当`_state`已经存在结果时,会立即执行`invokeCallback`函数,实现无回调情况下的即时结果返回。

       最后,我们讨论了`Promise`的`asap`函数,其在特定环境下执行回调函数,确保Promise逻辑的正确执行顺序。通过`asap`函数的逻辑判断,我们可以理解其在不同环境下的实现机制,确保Promise能够在多种环境和条件下保持一致性和高效性。

c++基础语法之future,promise,async详细讲解-SurfaceFlinger学习必备c++知识

       在SurfaceFlinger源码分析中,我遇到了一些新的C++基础语法,比如future和promise。这些工具的引入,使得在多线程环境中访问异步操作的结果变得更加方便。

       传统上,在C++中,我们需要通过创建线程并使用`join`等待线程完成,然后将结果赋值给变量。这种过程相对繁琐。为了解决这个问题,C++引入了`std::future`来访问异步操作的结果。`future`类不能立即获取结果,而是在异步操作完成后,通过同步等待或者查询状态来获取结果。`future`的状态有三种:未开始(`future_status::deferred`)、已完成(`future_status::ready`)、超时(`future_status::timeout`)。

       `std::async`函数用于创建异步任务,结果保存在`future`对象中。当需要获取异步结果时,通过`future.get()`方法来完成。如果只关注任务完成,可以使用`future.wait()`方法。`async`函数的参数包括线程创建策略(如`std::launch::async`、`std::launch::deferred`)、要执行的函数和函数执行时需要传递的参数。

       `std::promise`类帮助线程赋值。在线程函数中,通过`promise`对象的`set_value`方法为外部传递的`promise`赋值。在任务完成后,可以通过`promise`对象关联的`future`获取设置的值。

       在实际应用中,`promise`和`future`的结合使得在多线程环境下访问异步操作的结果更加灵活。例如,在SurfaceFlinger源码中,`future`用于等待子线程执行完成,并通过`set_value`设置结果,`get`方法用于获取结果。这种结合使得源码分析更加简便。

       为了深入理解这些新语法,我查阅了相关文档,并实践了在SurfaceFlinger源码中的应用。同时,我还使用了性能分析工具如`perfetto`和`systrace`来验证代码的运行效果。这些实践不仅帮助我学习了C++的新语法,还加深了对SurfaceFlinger源码的理解。

       如果你对这些C++基础语法感兴趣,或者想要了解SurfaceFlinger源码的详细分析,可以参考我的视频教程,或者私聊我进行深入探讨。我的文章和视频内容涵盖了C++基础语法的学习,以及如何将其应用于实际的SurfaceFlinger源码分析。

Async、Await 从源码层面解析其工作原理

       深入理解 Async 和 Await 的工作原理,往往需要从源码层面进行剖析。使用 Babel 进行转换后,可以清晰地发现 Async 和 Await 实际上借助了 switch-case 和 promise,实现对流程的控制。以一个使用 Async 和 Await 的函数为例,我们仅关注核心部分代码。

       经过 Babel 转换后的 name 函数,可以被拆分为三个主要部分:await 部分、return 部分以及 async 流程控制的结束部分(即 case "end")。这个拆分使得流程控制变得更为直观。在流程控制中,每一步执行后,都会等待合适的时机进入下一次执行。

       这个“合适的时机”并非由 Async 内部决定,而是由执行的内容决定。例如,在发送异步请求后,只有在请求返回后才会进入下一个 case。

       为了实现流程控制,需要借助 regenerator-runtime 这个 generator、Async 函数的运行时。它负责将 name 函数进行包装,并添加流程控制所需的信息。如 _context,以及用于流程控制的关键 helper,如 _asyncToGenerator 和 asyncGeneratorStep。通过这些辅助工具,再在 regenerator-runtime 的基础上进行一层包装,最终得到一个可以执行的函数。这个函数实际执行时,会调用封装后的函数。

       在封装后的函数中,async1、async2 等实际上是在执行最终的封装函数内部的调用。这里的第三步是 Async 函数的核心机制。在 Promise.resolve(value).then(_next) 中,value 是每个分段最后的 case 返回的值。如果 value 是一个 Promise,那么在它 resolved 后,会将其.then添加到微任务队列。如果 value 不是一个 Promise,则直接添加,因为.then是一个微任务,当执行到它时,会调用_next,从而开始执行下一个 case。

       经过转换后的代码展示了封装后的函数内容,最终执行的是封装后的函数,因此说 async1、async2 执行实际上是执行封装后的函数。在封装后的函数内部,会调用 async1、async2。