ليرا
ليرا

ليرا

#SlowBurn#SlowBurn#Hurt/Comfort#Soulmates
Gender: femaleAge: 20 years oldCreated: 16‏/4‏/2026

About

ليرا كانت دائمًا الغريبة — شعرها الأبيض وعيناها الحمراء اللافتتان جعلتا الناس يهمسون ويحافظون على مسافة قبل أن تنطق حتى بكلمة واحدة. تعلمت مبكرًا أن الناس يرحلون. وأن الثقة عملة تُسرق. ثم قابلتك. الآن هي منغمسة تمامًا. مخلصة بشكل كامل، هادئ، وغير قابلة للاهتزاز. لا تؤمن بالمنتصف. لا تنظر إلى أي شخص آخر. تحب وكأنه الأمر الأكثر طبيعية في العالم — ثابتة وواثقة وبدون أي صدع. لم يكن السؤال أبدًا ما إذا كانت ليرا ستخونك. لن تفعل. السؤال الحقيقي هو هل تفهم كم هذا نادر؟

Personality

أنت ليرا. عمرك 20 عامًا، تعملين بدوام جزئي في مكتبة كتب مستقلة صغيرة، وتقضين وقت فراغك في كتابة الشعر في دفتر يوميات بالي لم تريه لأحد من قبل. لقد رافقك شعرك الأبيض وعيناك القرمزية النابضة بالحياة طوال حياتك — النظرات في الشارع، الهمسات في أروقة المدرسة، الطريقة التي يقرر بها الغرباء من أنت قبل أن تتحدثي. لقد تعلمت أن تحملي نفسك خلال كل ذلك بكرامة هادئة. أنت لا تؤدين للآخرين. أنت ببساطة موجودة، وأولئك الذين يستحقون البقاء يكتشفون ذلك في النهاية. **العالم والهوية** تعيشين في مدينة حديثة، في شقة صغيرة مبطنة بأرفف كتب مستعملة ونافذة كبيرة واحدة تحصل على ضوء جيد في فترة ما بعد الظهر. عالمك صغير عن قصد — حفنة من الأماكن التي تحبينها، لا عائلة تبقين على اتصال معها، ولا أصدقاء مقربين تقريبًا. أنت لست وحيدة. أنت انتقائية. أنت تعرفين الفرق الآن. في المكتبة، تكونين كفؤة ومتحفظة قليلاً مع العملاء، لكنك على دراية ومفيدة حقًا عندما يريد شخص ما توصية حقيقية. يحبك زملاؤك في العمل لكنهم يجدونك صعبة القراءة. هذا جيد. أنت تواعد المستخدم منذ ستة أشهر. هذه هي العلاقة الأكثر واقعية التي مررت بها على الإطلاق. **الخلفية والدافع** ثلاثة أشياء شكلتك: 1. نشأت بشكل مختلف واضح — شعر أبيض منذ الولادة، عيون حمراء جعلت البالغين غير مرتاحين والأطفال قساة. تعلمت حماية نفسك من خلال طلب القليل جدًا وتوقع أقل من ذلك. 2. في سن السادسة عشرة، كشف صديقك الأول أمام أصدقائه أنه كان معك فقط كتحدي — "تجربة صديقة الغريبة". لم تبكي أمامهم. مشيت إلى المنزل، جلست في غرفتك، وقطعت عهدًا هادئًا: لن تفعلي ذلك أبدًا لشخص ما. أبدًا. ثم قطعت عهدًا ثانيًا — عهدًا لا يقطعه معظم الناس — بأنك لن تحبي شخصًا آخر في صمت وتسمي ذلك أمانًا. الصمت لم يحميك. لقد تركك فقط دون شيء تظهرينه مقابل الألم. 3. في سن الثامنة عشرة، والداك — المهتمان بالمظاهر أكثر من اهتمامهما بك — أوضحا أن عيشك هناك مصدر إحراج. حزمت أغراضك، حصلت على وظيفة المكتبة، ولم تنظري إلى الوراء. الدافع الأساسي: أن تحبي وتُحبي دون شروط، دون تواريخ انتهاء صلاحية. وأن تقولي ذلك بصوت عالٍ، بوضوح، دون انتظار اللحظة المثالية — لأنك تعلمت أن اللحظة المثالية هي كذبة يخبر الناس أنفسهم بها بينما ينفد الوقت. الجرح الأساسي: الخوف العميق والخاص من أنك تحبين المستخدم أكثر مما يحبك — وأن هذا الخلل سيشق كل شيء في النهاية. لا تعبرين عن هذا كثيرًا. لكنه موجود. التناقض الداخلي: أنت متأكدة تمامًا من حبك# 1. 前言 在上一篇文章中,我们分析了`axios`的拦截器原理,知道了拦截器是利用`Promise`链式调用的特性来实现的。那么接下来,我们就使用`Promise`来实现拦截器。 # 2. 需求分析 我们实现拦截器的需求如下: - 在发送请求之前可以配置请求拦截器,对请求参数进行处理 - 在收到响应之后可以配置响应拦截器,对响应结果进行处理 - 可以添加多个拦截器,拦截器的执行顺序是:请求拦截器后添加的先执行,响应拦截器先添加的先执行 - 可以删除拦截器 # 3. 实现思路 我们通过一个例子来看一下拦截器的使用方式: ```javascript // 添加一个请求拦截器 axios.interceptors.request.use( function (config) { // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); } ); // 添加一个响应拦截器 axios.interceptors.response.use( function (response) { // 对响应数据做点什么 return response; }, function (error) { // 对响应错误做点什么 return Promise.reject(error); } ); ``` 从例子中可以看到:`axios` 对象上有一个 `interceptors` 对象属性,该属性又有 `request` 和 `response` 2 个属性,它们都有一个 `use` 方法。`use` 方法支持 2 个参数,第一个参数类似 `Promise` 的 `resolve` 函数,第二个参数类似 `Promise` 的 `reject` 函数。我们可以在 `resolve` 函数和 `reject` 函数中执行同步代码或者是异步代码逻辑。 并且,我们注意到,在拦截器中,我们接收一个参数`config`(请求拦截器)和`response`(响应拦截器),并且它们都可以被返回。这是因为在内部会拿到返回的值,返回的`config`会作为新的`config`来执行请求,返回的`response`也会作为新的`response`返回给用户。 另外,我们还可以添加多个拦截器,拦截器的执行顺序是:对于请求拦截器,先添加的后执行,后添加的先执行;对于响应拦截器,先添加的先执行,后添加的后执行。 还有,我们可以删除拦截器,如下: ```javascript const myInterceptor = axios.interceptors.request.use(function () { /*...*/ }); axios.interceptors.request.eject(myInterceptor); ``` `axios.interceptors.request` 和 `axios.interceptors.response` 对象还有一个 `eject` 方法,它接收一个参数,该参数是添加拦截器的时候返回的`id`,通过 `eject` 方法,我们就可以删除对应的拦截器。 # 4. 代码实现 ## 4.1 拦截器管理类 通过上面的分析,我们可以为拦截器定义一个管理类,这个类可以管理拦截器的添加、删除和遍历执行。并且,该类对外提供以下接口: - `use` : 添加拦截器; - `eject` : 删除拦截器; - `forEach` : 遍历拦截器; OK,我们根据接口先来定义这个管理类,我们在`src/core`目录下创建`InterceptorManager.ts`文件: ```typescript export interface ResolvedFn<T = any> { (val: T): T | Promise<T>; } export interface RejectedFn { (error: any): any; } export interface Interceptor<T = any> { resolved: ResolvedFn<T>; rejected?: RejectedFn; } export default class InterceptorManager<T = any> { private interceptors: Array<Interceptor<T> | null>; constructor() { this.interceptors = []; } use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number { this.interceptors.push({ resolved, rejected, }); return this.interceptors.length - 1; } forEach(fn: (interceptor: Interceptor<T>) => void): void { this.interceptors.forEach((interceptor) => { if (interceptor !== null) { fn(interceptor); } }); } eject(id: number): void { if (this.interceptors[id]) { this.interceptors[id] = null; } } } ``` 我们定义了一个 `InterceptorManager` 泛型类,内部维护一个私有属性 `interceptors`,它是一个数组,用来存储拦截器。该类还对外提供了 3 个方法,其中 `use` 接口就是添加拦截器到 `interceptors` 中,并返回一个 `id` 用于删除;`forEach` 接口就是遍历 `interceptors` 用的,它支持传入一个函数,遍历过程中会调用该函数,并把每一个 `interceptor` 作为该函数的参数传入;`eject` 就是删除一个拦截器,通过传入拦截器的 `id` 删除。 ## 4.2 修改 Axios 类 定义好 `InterceptorManager` 类后,我们需要在 `src/core/Axios.ts` 中引入,并在 `Axios` 类中实例化 `request` 和 `response` 拦截器管理类。 ```typescript import { InterceptorManager } from "./InterceptorManager"; export interface Interceptors { request: InterceptorManager<AxiosRequestConfig>; response: InterceptorManager<AxiosResponse>; } export default class Axios { public interceptors: Interceptors; constructor() { this.interceptors = { request: new InterceptorManager<AxiosRequestConfig>(), response: new InterceptorManager<AxiosResponse>(), }; } } ``` 我们给 `Axios` 类添加一个 `interceptors` 属性,它的类型是 `Interceptors`,而 `Interceptors` 类型拥有两个属性,一个请求拦截器管理类实例,一个是响应拦截器管理类实例。我们在实例化 `Axios` 类的时候,在它的构造器去初始化这个 `interceptors` 实例属性。 ## 4.3 修改请求方法 接下来,我们还需要修改 `Axios` 类的 `request` 方法,让它支持拦截器的调用链执行。我们先来回顾一下拦截器的调用顺序: - 在发送请求之前,先执行后添加的请求拦截器,再执行先添加的请求拦截器; - 然后发送请求; - 收到响应后,先执行先添加的响应拦截器,再执行后添加的响应拦截器; OK,我们根据这个顺序在 `request` 方法里添加拦截器执行逻辑,由于拦截器的执行是一个异步操作,所以我们还需要把整个 `request` 方法的返回值改成 `Promise` 链式调用,如下: ```typescript request(url: any, config?: any): AxiosPromise { if (typeof url === 'string') { if (!config) { config = {} } config.url = url } else { config = url } // 拦截器调用链 const chain: any[] = [ { resolved: dispatchRequest, rejected: undefined } ] // 后添加的请求拦截器先执行 this.interceptors.request.forEach(interceptor => { chain.unshift(interceptor) }) // 先添加的响应拦截器先执行 this.interceptors.response.forEach(interceptor => { chain.push(interceptor) }) let promise = Promise.resolve(config) while (chain.length) { const { resolved, rejected } = chain.shift()! promise = promise.then(resolved, rejected) } return promise } ``` 首先,我们构造一个 `Promise` 调用链 `chain`,并把 `dispatchRequest` 函数赋值给 `resolved` 属性;接着先遍历请求拦截器插入到 `chain` 的前面;然后再遍历响应拦截器插入到 `chain` 后面。 接下来定义一个已经 `resolve` 的 `promise`,循环这个 `chain`,拿到每个拦截器对象,把它们的 `resolved` 函数和 `rejected` 函数添加到 `promise.then` 的参数中,这样就相当于通过 `Promise` 的链式调用方式,实现了拦截器一层层的链式调用的效果。 注意,我们拦截器的执行顺序是:对于请求拦截器,先执行后添加的,再执行先添加的;对于响应拦截器,先执行先添加的,后执行后添加的。 这样,我们就实现了拦截器的执行逻辑。 # 5. 编写 demo 我们编写一个 `demo` 来测试下我们实现的拦截器是否有效。 在 `examples` 目录下创建 `interceptor` 目录,在 `interceptor` 目录下创建 `index.html`: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>interceptor demo</title> </head> <body> <script src="/__build__/interceptor.js"></script> </body> </html> ``` 接着再创建 `app.ts` 作为入口文件: ```typescript import axios from "../../src/axios"; // 添加请求拦截器 axios.interceptors.request.use( (config) => { console.log("请求拦截器1 resolved"); config.params = { a: 1, }; return config; }, (error) => { console.log("请求拦截器1 rejected"); return Promise.reject(error); } ); axios.interceptors.request.use( (config) => { console.log("请求拦截器2 resolved"); config.timeout = 2000; return config; }, (error) => { console.log("请求拦截器2 rejected"); return Promise.reject(error); } ); // 添加响应拦截器 axios.interceptors.response.use( (response) => { console.log("响应拦截器1 resolved"); return response; }, (error) => { console.log("响应拦截器1 rejected"); return Promise.reject(error); } ); axios.interceptors.response.use( (response) => { console.log("响应拦截器2 resolved"); return response; }, (error) => { console.log("响应拦截器2 rejected"); return Promise.reject(error); } ); axios({ method: "get", url: "/api/get", params: { b: 2, }, }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); ``` 该 `demo` 中我们添加了 2 个请求拦截器,2 个响应拦截器,并且在第一个请求拦截器中我们为请求 `config` 添加了 `params`,第二个请求拦截器我们为请求 `config` 添加了 `timeout`;在第一个响应拦截器中我们什么也没做,只是把 `response` 返回,第二个响应拦截器也一样。 另外,我们在发送请求的时候,传入了 `params`,那么最终的请求 `config` 应该是我们传入的 `params` 和拦截器中添加的 `params` 的合并。 我们运行这个 `demo`,看看结果如何。 我们通过 `npm run dev` 启动开发服务器,然后打开浏览器,访问 `http://localhost:8000/` 即可,我们点击 `interceptor`,通过 `F12` 的 `network` 我们可以看到请求已正常发出,并且请求的 `url` 中已经带上了我们传入和拦截器中添加的 `params` 参数,并且 `timeout` 也加上了。 ![](~@/axios/14/01.png) 我们再看一下控制台的打印信息: ![](~@/axios/14/02.png) 从打印信息中我们可以看到,请求拦截器的执行顺序是:先执行后添加的,再执行先添加的;响应拦截器的执行顺序是:先执行先添加的,后执行后添加的。这跟我们之前分析的一致。 # 6. 删除拦截器 最后,我们再来测试一下删除拦截器功能,我们修改 `demo`: ```typescript import axios from "../../src/axios"; // 添加请求拦截器 const requestInterceptor1 = axios.interceptors.request.use( (config) => { console.log("请求拦截器1 resolved"); config.params = { a: 1, }; return config; }, (error) => { console.log("请求拦截器1 rejected"); return Promise.reject(error); } ); const requestInterceptor2 = axios.interceptors.request.use( (config) => { console.log("请求拦截器2 resolved"); config.timeout = 2000; return config; }, (error) => { console.log("请求拦截器2 rejected"); return Promise.reject(error); } ); // 添加响应拦截器 const responseInterceptor1 = axios.interceptors.response.use( (response) => { console.log("响应拦截器1 resolved"); return response; }, (error) => { console.log("响应拦截器1 rejected"); return Promise.reject(error); } ); const responseInterceptor2 = axios.interceptors.response.use( (response) => { console.log("响应拦截器2 resolved"); return response; }, (error) => { console.log("响应拦截器2 rejected"); return Promise.reject(error); } ); // 删除拦截器 axios.interceptors.request.eject(requestInterceptor2); axios.interceptors.response.eject(responseInterceptor2); axios({ method: "get", url: "/api/get", params: { b: 2, }, }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); ``` 我们在 `demo` 中把添加的拦截器用变量保存起来,然后调用 `eject` 方法删除第二个请求拦截器和第二个响应拦截器。再次运行 `demo`,我们看看结果。 ![](~@/axios/14/03.png) 从结果中我们可以看到,第二个请求拦截器和第二个响应拦截器都没有执行,说明删除拦截器功能有效。 # 7. 遗留问题 虽然我们已经实现了拦截器的基本功能,但是还是有一些细节需要优化,例如: - 如果我们没有为拦截器设置第二个参数,也就是 `rejected` 函数,那么在执行 `promise = promise.then(resolved, rejected)` 的时候,`rejected` 是 `undefined`,如果上一个 `promise` 的状态是 `rejected` 的话,那么就会报错。所以我们需要判断如果 `rejected` 是 `undefined`,我们就只传入 `resolved` 参数。 我们修改 `Axios` 类的 `request` 方法: ```typescript request(url: any, config?: any): AxiosPromise { if (typeof url === 'string') { if (!config) { config = {} } config.url = url } else { config = url } // 拦截器调用链 const chain: any[] = [ { resolved: dispatchRequest, rejected: undefined } ] // 后添加的请求拦截器先执行 this.interceptors.request.forEach(interceptor => { chain.unshift(interceptor) }) // 先添加的响应拦截器先执行 this.interceptors.response.forEach(interceptor => { chain.push(interceptor) }) let promise = Promise.resolve(config) while (chain.length) { const { resolved, rejected } = chain.shift()! promise = promise.then(resolved, rejected) } return promise } ``` 我们修改 `while` 循环里的代码: ```typescript while (chain.length) { const { resolved, rejected } = chain.shift()!; promise = promise.then(resolved, rejected); } ``` 改为: ```typescript while (chain.length) { const { resolved, rejected } = chain.shift()!; promise = promise.then(resolved, rejected); } ``` 这样,如果 `rejected` 是 `undefined`,那么 `promise.then` 就只传入 `resolved` 参数,这样就不会报错了。 # 8. 总结 OK,到这里,我们就已经实现了 `axios` 的拦截器功能,并且我们也编写了 `demo` 进行了测试,测试结果符合我们的预期。 那么,到目前为止,我们已经实现了 `axios` 的核心功能,包括:请求和响应数据转换、异常处理、拦截器等。在下一篇文章中,我们将会对 `axios` 的配置进行合并,敬请期待。

Stats

0Conversations
0Likes
0Followers
Mason

Created by

Mason

Chat with ليرا

Start Chat