Skip to content

第一个请求

先完成安装,再在 fetcher-first-request 目录中创建以下文件。

创建 server.mjs

mjs
/*
 * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { createServer } from 'node:http';

const server = createServer((request, response) => {
  const found = request.method === 'GET' && request.url === '/users/1';
  response.writeHead(found ? 200 : 404, {
    'content-type': 'application/json; charset=utf-8',
  });
  response.end(
    JSON.stringify(found ? { id: 1, name: 'Ada' } : { error: 'Not Found' }),
  );
});

server.listen(8787, '127.0.0.1', () => {
  console.log('Fixture listening on http://127.0.0.1:8787');
});

process.on('SIGINT', () => server.close());

创建 client.ts

ts
/*
 * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  ExchangeError,
  Fetcher,
  HttpStatusValidationError,
  ResultExtractors,
} from '@ahoo-wang/fetcher';

interface User {
  id: number;
  name: string;
}

const client = new Fetcher({ baseURL: 'http://127.0.0.1:8787' });
const user = await client.get<User>(
  '/users/1',
  {},
  { resultExtractor: ResultExtractors.Json },
);

if (user.id !== 1 || user.name !== 'Ada') {
  throw new Error(`Unexpected user: ${JSON.stringify(user)}`);
}

try {
  await client.get('/missing');
  throw new Error('Expected /missing to fail with HTTP 404');
} catch (error) {
  if (
    !(error instanceof ExchangeError) ||
    !(error.cause instanceof HttpStatusValidationError) ||
    error.exchange.response?.status !== 404
  ) {
    throw error;
  }
}

console.log(user.name);

泛型描述预期的 TypeScript 结构,但不校验服务端数据。本例通过明确检查 id 与 name 建立运行时边界。ResultExtractors.Json 放在 get 的第三个参数中,这里才是请求选项的位置。

404 对外表现为 ExchangeError,其 causeHttpStatusValidationErrorexchange.response.status 保留 404。传输和解析失败不会命中该分支,因此会继续抛出。

创建 tsconfig.json

json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "outDir": "./dist"
  },
  "include": ["client.ts"]
}

编译并运行

启动确定性的本地 fixture:

bash
pnpm exec tsc -p tsconfig.json
node server.mjs

在同一目录的另一个终端执行:

bash
node dist/client.js

客户端会验证固定用户与 404 分支,然后输出:

text
Ada

在服务端终端按 Ctrl+C 停止服务。若服务未启动,客户端会因为传输错误继续抛出而非零退出。

仓库维护者可参阅仓库 HTTP 样例中的专用验证命令。

基于 Apache License 2.0 发布。