ReactPHP 异步操作封装

ReactPHP 异步操作封装

功能概述

封装一个异步操作方法,确保传入的函数不会阻塞后续代码执行。

依赖安装

1
composer require react/event-loop react/promise

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

use React\EventLoop\Loop;
use React\Promise\Deferred;

/**
* 异步执行回调函数,不阻塞事件循环
* @param callable $callback 需要异步执行的回调函数
* @return \React\Promise\Promise 返回 Promise 对象用于处理结果
*/
function asyncOperation(callable $callback): \React\Promise\Promise
{
$deferred = new Deferred();

// 获取当前事件循环实例
$loop = Loop::get();

// 在下一个事件循环 tick 中执行回调
$loop->futureTick(function () use ($callback, $deferred) {
try {
// 执行回调并获取结果
$result = $callback();
$deferred->resolve($result);
} catch (\Throwable $e) {
// 捕获异常并拒绝 promise
$deferred->reject($e);
}
});

return $deferred->promise();
}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

require __DIR__ . '/vendor/autoload.php';

// 正确使用示例(非阻塞)
asyncOperation(function () {
// 模拟异步操作(不能使用同步阻塞代码)
return '异步操作完成';
})->then(function ($result) {
echo $result; // 输出:异步操作完成
})->otherwise(function (\Throwable $e) {
echo '出错:' . $e->getMessage();
});

// 后续代码会立即执行
echo "立即执行的内容\n";

// 错误使用示例(同步阻塞代码仍然会阻塞)
asyncOperation(function () {
sleep(2); // 同步阻塞操作
return '这仍然会阻塞2秒';
})->then(function ($result) {
echo $result;
});

注意事项

  1. 不要使用同步阻塞代码

    • 避免 sleep()file_get_contents() 等同步阻塞操作
    • 使用 ReactPHP 提供的异步替代方案
  2. 推荐异步操作方式

    1
    2
    3
    4
    // 正确:使用 ReactPHP 的异步 HTTP 客户端
    asyncOperation(function () use ($client) {
    return $client->request('GET', 'https://api.example.com');
    });
  3. CPU 密集型任务

    • 建议使用多进程方案处理
    • 可考虑 workerman 等扩展

常见问题

Q: 为什么我的异步操作仍然阻塞?
A: 检查回调函数中是否包含同步阻塞代码,如 sleep()file_get_contents()

Q: 如何处理 CPU 密集型任务?
A: 使用多进程方案,ReactPHP 不适合处理 CPU 密集型任务

完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

require __DIR__ . '/vendor/autoload.php';

// 异步执行多个任务
$promises = [
asyncOperation(function () {
return "任务1完成";
}),
asyncOperation(function () {
return "任务2完成";
}),
asyncOperation(function () {
return "任务3完成";
})
];

// 等待所有任务完成
React\Promise\all($promises)->then(function ($results) {
print_r($results);
})->otherwise(function (\Throwable $e) {
echo '出错:' . $e->getMessage();
});

// 保持事件循环运行(实际应用中通常由 HTTP 服务器等保持)
Loop::get()->addTimer(1, function () {
Loop::get()->stop();
});

这个封装提供了简单易用的异步操作方式,适用于大多数 ReactPHP 异步编程场景。

TypeScript 映射类型与条件类型(附形象记忆法)

TypeScript 映射类型与条件类型(附形象记忆法)

一、映射类型(Mapped Types)详解

1.1 基础概念

映射类型允许我们基于现有类型的属性,创建一个新类型,通常用于批量修改属性。

核心语法

1
2
3
type MappedType<T> = {
[P in keyof T]: /* 对属性P的操作 */
};

1.2 常见内置映射类型

1.2.1 Readonly<T>

将所有属性设为只读:

1
2
3
4
5
6
7
8
9
10
11
interface User {
name: string;
age: number;
}

type ReadonlyUser = Readonly<User>;
// 等同于:
// {
// readonly name: string;
// readonly age: number;
// }

形象记忆:就像给所有门加上”禁止进入”的锁(readonly

1.2.2 Partial<T>

将所有属性变为可选:

1
2
3
4
5
6
type PartialUser = Partial<User>;
// 等同于:
// {
// name?: string;
// age?: number;
// }

形象记忆:就像把所有必填字段变成”选填”(?

1.2.3 Required<T>

将所有属性变为必填(与 Partial 相反)

1
2
type RequiredUser = Required<PartialUser>;
// 恢复所有属性为必填

1.2.4 Record<K, T>

创建一个对象类型,其键为 K,值为 T

1
2
type UserRoles = Record<string, "admin" | "user" | "guest">;
// 等同于 { [key: string]: "admin" | "user" | "guest" }

形象记忆:就像创建一个”键值对”的表格

1.2.5 Pick<T, K>

从 T 中选取指定的属性 K

1
2
type UserName = Pick<User, "name">;
// 等同于 { name: string }

形象记忆:就像从菜单中”点菜”(只选需要的)

1.2.6 Omit<T, K>

从 T 中排除指定的属性 K

1
2
type UserWithoutAge = Omit<User, "age">;
// 等同于 { name: string }

形象记忆:就像从套餐中”删除”不想要的项


1.3 自定义映射类型

1.3.1 添加前缀/后缀

1
2
3
4
5
6
type AddPrefix<T, Prefix extends string> = {
[P in keyof T as `${Prefix}${Capitalize<string & P>}`]: T[P];
};

type PrefixedUser = AddPrefix<User, "user_">;
// 等同于 { user_name: string; user_age: number }

形象记忆:就像给所有文件名加前缀

1.3.2 条件修改属性

1
2
3
4
5
6
type MakeNullable<T> = {
[P in keyof T]: T[P] | null;
};

type NullableUser = MakeNullable<User>;
// 等同于 { name: string | null; age: number | null }

形象记忆:就像给所有必填字段加上”可为空”的选项


二、条件类型(Conditional Types)详解

2.1 基础语法

1
T extends U ? X : Y

如果类型 T 可以赋值给 U,则结果为 X,否则为 Y

2.2 常见用法

2.2.1 类型判断

1
2
3
4
type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

形象记忆:就像做选择题(是/否)

2.2.2 提取数组元素类型

1
2
3
4
5
type ElementType<T> = T extends (infer U)[] ? U : never;

type Str = ElementType<string[]>; // string
type Num = ElementType<number[]>; // number
type NeverArr = ElementType<number>; // never

形象记忆:就像从容器中”提取”内容

2.2.3 分支处理

1
2
3
4
5
6
7
8
9
type ResponseType<T> = T extends { error: any }
? "error"
: T extends { data: any }
? "success"
: "unknown";

type Success = ResponseType<{ data: string }>; // "success"
type Error = ResponseType<{ error: string }>; // "error"
type Unknown = ResponseType<{ status: number }>; // "unknown"

形象记忆:就像交通信号灯(不同条件走不同分支)


2.3 高级用法

2.3.1 分布式条件类型

当 T 是联合类型时,条件类型会自动分发:

1
2
3
4
type ToArray<T> = T extends any ? T[] : never;

type StrOrNumArray = ToArray<string | number>;
// 等同于 string[] | number[]

形象记忆:就像批量处理邮件(每个收件人收到单独的邮件)

2.3.2 条件类型递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type DeepReadonly<T> = {
[P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

interface Nested {
a: number;
b: {
c: string;
d: {
e: boolean;
};
};
}

type ReadonlyNested = DeepReadonly<Nested>;
// 所有层级都变为readonly

形象记忆:就像俄罗斯套娃,一层层深入


三、形象记忆法总结

概念 记忆技巧
Readonly<T> 给所有属性加”禁止修改”标签
Partial<T> 把所有必填变成”选填”
Pick<T,K> 从菜单中”点菜”
Omit<T,K> 从套餐中”删除”不想要的
AddPrefix<T,P> 给文件名加前缀
MakeNullable<T> 给必填字段加”可为空”选项
IsString<T> 做选择题(是/否)
ElementType<T> 从容器中”提取”内容
分布式条件类型 批量处理邮件(每个收件人单独处理)
条件类型递归 俄罗斯套娃,一层层深入

在Pinia中如何持久化存储带函数的对象

在使用 Pinia 进行状态管理时,常规方法难以直接持久化存储包含函数的对象。因为 JSON.stringify()在序列化对象时会忽略函数。本文将介绍一种自定义的解决方案,实现包含函数对象的持久化。

1. 安装依赖

确保项目中已经安装了 Pinia。如果没有安装,可以通过 npm 进行安装:

1
npm install pinia

2. 自定义持久化逻辑

实现自定义的持久化逻辑,通过将函数序列化为字符串存储,在恢复时再转换回函数。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import { defineStore } from "pinia";

// 自定义持久化逻辑
const loadState = () => {
const state = localStorage.getItem("myStore");
if (state) {
const parsedState = JSON.parse(state);
// 将存储的函数字符串转换回函数
if (
parsedState.myObject &&
typeof parsedState.myObject.sayHello === "string"
) {
parsedState.myObject.sayHello = new Function(
`return ${parsedState.myObject.sayHello}`
)();
}
return parsedState;
}
return null;
};

const saveState = (state) => {
// 将函数转换为字符串存储
const stateToSave = { ...state };
if (
stateToSave.myObject &&
typeof stateToSave.myObject.sayHello === "function"
) {
stateToSave.myObject.sayHello = stateToSave.myObject.sayHello.toString();
}
localStorage.setItem("myStore", JSON.stringify(stateToSave));
};

// 定义包含函数的对象
const myObjectWithFunction = {
message: "Hello from Pinia!",
sayHello: function () {
console.log(this.message);
},
};

// 定义Pinia存储
export const useMyStore = defineStore("myStore", {
state: () => {
const savedState = loadState();
return (
savedState || {
myObject: myObjectWithFunction,
}
);
},
actions: {
updateMessage(newMessage) {
this.myObject.message = newMessage;
saveState(this.$state);
},
},
// 在存储更新时调用保存逻辑
onBeforeUpdate: () => {
saveState(useMyStore().$state);
},
});

3. 在组件中使用

在 Vue 3 组件中使用上述定义的 Pinia 存储。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<button @click="callFunctionInObject">Call Function in Object</button>
<input v-model="newMessage" placeholder="Enter new message" />
<button @click="updateMessage">Update Message</button>
</div>
</template>

<script setup>
import { ref } from "vue";
import { useMyStore } from "./store";

const store = useMyStore();
const newMessage = ref("");

const callFunctionInObject = () => {
store.myObject.sayHello();
};

const updateMessage = () => {
store.updateMessage(newMessage.value);
};
</script>

注意事项

  1. 安全风险:使用new Function()存在安全风险,如果存储的函数字符串来自不可信源,可能会执行恶意代码。
  2. 复杂场景:函数序列化可能无法处理所有复杂情况,如函数内部引用了外部变量。

通过上述方法,我们能够在 Pinia 中实现包含函数对象的持久化存储,解决常规持久化方案无法处理函数的问题。

GIT贮藏

在 Git 里,贮藏(stash)能够把你工作目录里未提交的修改保存起来,让你可以在之后恢复这些修改。

1. 检查当前工作目录状态

在建立贮藏之前,你可以使用以下命令查看当前工作目录的状态:

1
git status

该命令会显示哪些文件被修改、添加或者删除。

2. 创建贮藏

若你要把所有未提交的修改(包括已暂存和未暂存的)保存到一个贮藏中,可以使用下面的命令:

1
git stash save "可选的贮藏描述信息"

例如:

1
git stash save "临时保存未完成的功能修改"

如果你仅想贮藏已暂存的文件,可使用:

1
git stash push -p

然后按提示选择要贮藏的修改。

3. 查看贮藏列表

创建贮藏之后,你可以使用以下命令查看贮藏列表:

1
git stash list

输出结果可能如下:

1
stash@{0}: On master: 临时保存未完成的功能修改

4. 恢复贮藏

若你想恢复最近一次的贮藏,可以使用:

1
git stash apply

要是你想恢复特定的贮藏,可指定贮藏的编号,例如恢复stash@{0}

1
git stash apply stash@{0}

恢复贮藏后,该贮藏依旧存在于贮藏列表中。若你想在恢复贮藏的同时将其从贮藏列表里移除,可以使用pop命令:

1
git stash pop

5. 删除贮藏

如果你想删除某个贮藏,可以使用以下命令:

1
git stash drop stash@{0}

若要删除所有贮藏,可使用:

1
git stash clear