最近在研究nodejs,尝试在nodejs中使用fetch 来发起请求,从网上抄下来这么一段代码:

// demo01.js
import fetch from "node-fetch";
fetch("https://csdn.net")
  .then(res=>res.text())
  .then(json=>console.log(json));

直接使用肯定是不行的,这里一看就需要依赖 node-fetch 这个模块,于是我没有想别的,使用 npm 安装了这个模块(为了方便,我直接将其安装称为了全局模块了):

npm install -g node-fetch

做完以上步骤后,我的工程内目前只有一个 demo01.js 装着代码的一个文件,没有其他任何资源。
在这里插入图片描述

然后在代码文件夹中打开终端,尝试在终端中运行该代码:

node .\demo01.js

然后就报错了:
在这里插入图片描述

SyntaxError: nodejs Cannot use import statement outside a module

作为一个小白,请原谅我只在浏览器中运行过 fetch ,报这样的错误我哪见过这样的错误,看起来好像是不支持 import 难道是我的 nodejs 版本有问题?

应用版本
nodejs12.20.1
node-fetch3.0.0

在这里插入图片描述在这里插入图片描述

看起来好像也是很很新的版本了啊。其实这里的错误提示已经给的很明显了,只是我刚开始学习 nodejs 导致没有明白其中报错的含义,以为是nodejs 版本的原因。
但实际原因是 import 关键字是 ES6 的一个特性,而 nodejs 默认场景下是支持 AMD,CommonJS,

首先我们可确认的是我们使用到了一个模块 node-fetch,而在 nodejs 中对于模块的调用方式有两种,分别对应 ES5 规范 和 ES6 规范,其中 ES5 规范对应的是 require 函数,通过调用 require 函数的方式引入模块;而 ES6 规范引入了 import 关键字,通过该关键字引入模块。

// ES5 的模块引入方式
const fetch = require("node-fetch");

// ES6 的模块引入方式
import fetch from "node-fetch";

因此从网上找到的一些nodejs 调用 fetch的示例代码一般都使用到了这两种方式,如 node-fetch 的示例代码使用的是 import 关键字:

还有一些教程则使用的是 require 关键字:

出于版本升级的原因,nodejs 中默认是通过 require 关键字的方式加载模块的,也就是如果你直接通过 import 调用模块执行的话,就会出现最开始我遇到的错误:
在这里插入图片描述
报错信息中指出当前如果直接使用 node demo01.js 的方式执行代码的话,是一种非模块话的方式调用,在这种调用方式下无法直接使用 import 关键字。如果要使用的话,需要在 package.json 文件中指定当前工程是一个模块,在能够在模块中使用 import 关键字。

这里说明一下 package.json 是一个包配置文件,可以由 npm init 在当前工程的根目录下自动生成,也可以自己手动编写。npm 通过该配置文件可以实现对该工程中依赖的模块进行管理,比如希望在当前工程中添加对 node-fetch的依赖,首先使用 npm init在当前目录下创建 package.json 文件,然后执行命令 npm install node-fetch --save 就会将对 node-fetch的依赖写入到 package.json 中。
在这里插入图片描述
其中 --save选项用于表示将对 node-fetch 的依赖写入到 package.json 当中去,在新版本中即使不指定 --save选项也会默认写入的。

在执行完上述操作后可以看到当前工程下会存在一个配置文件(package.json):

{
  "name": "dem01",
  "version": "1.0.0",
  "description": "",
  "main": "demo01.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

有了这个文件后我们的代码其实就可以打包使用了,但这不是我这里想要讲的内容,想要深入了解的同学可以去看下面的参考资料。

现在我们有了 package.json ,我们尝试按要求往里面添加一个 "type":"module",标记当前工程为一个模块:
在这里插入图片描述
重新安装一下我们的依赖 node-fetch 再次尝试运行我们的脚本:

node demo01.js

可以看到我们的代码已经可以正常执行了。

import 方式执行代码

首先确认下import 方式引入 node-fetch 是在 node-fetch 的最新版本 v3.x出现的,而 node-fetchv3.x 版本要求 nodejs版本大于 12.20.0

因此在使用 import 方式导入 node-fetch 模块不仅要求 node-fetch 版本为 v3.x
还要求 nodejs 版本 大于 12.20.0,需要注意一下。

# 新建一个工程目录;
mkdir demo01 && cd demo01
# 初始化当前模块信息
npm init
# 指定模块类型为 module
vim package.json
# 查看 node-fetch 信息
npm info node-fetch
# 查看 node-fetch 所有版本
npm view node-fetch versions
# 安装 node-fetch 并指定版本为 3.0.0
npm install node-fetch@3.0.0

编写 js 脚本

import fetch from "node-fetch";
fetch("https://csdn.net")
  .then(res=>res.text())
  .then(json=>console.log(json));

运行:

node demo01.js

运行成功。

require 方式执行代码

再深究一步,在网上可以看到很多使用 require 方式引入node-fetch的,为了更进一步研究其中的机制,决定重新使用 require 的方式将上面的代码重新写一遍:

// demo01.js
// import fetch from "node-fetch";
const fetch = require("node-fetch");
fetch("https://csdn.net")
  .then(res=>res.text())
  .then(json=>console.log(json));

运行:

在这里插入图片描述

可以看到发生了错误,第一行错误表示必须使用 import 关键字来加载一个 ES 模块。
第二行错误说明了为什么会将当前工程标识为一个 ES 模块,并不仅仅是根据当前工程下 package.json文件中存在 "type":"module",来进行标记的,而是根据当前工程下依赖的模块 node-fetch模块中 package.json 中存在"type":"module",因此判断当前工程是一个 ES 模块。也就是说只要工程或者 工程所依赖的代码中有一个被标记为 ES 模块,那么整个工程就会被认为是一个 ES 模块。模块之间的调用也就只能够使用 import关键字而不能够使用 require函数。

解决方案

那是网上找到的参考教程出错了吗?其实也不是,这里涉及到一个 node-fetch版本升级的问题,也就是 node-fetch存在一个大的版本升级,具体点讲就是node-fetchv2.x 升级到 v3.x 的时候,其引入方式存在着大的更新,从之前的 require 修改为了 import ,并且要求 nodejs 版本需要大于 12.20.0

如果发现你的 node-fetch 不能够使用的话,请检查一下你的 node-fetch版本是否为 v3.xnodejs 版本是否大于 v12.20.0。如果nodejs 版本小于 v12.20.0,那么需要指定 node-fetch 的版本在 v2.x上,然后使用 require 方式调用:

# 新建一个工程目录;
mkdir demo02 && cd demo02
# 查看 node-fetch 信息
npm info node-fetch
# 查看 node-fetch 所有版本
npm view node-fetch versions
# 安装 node-fetch 并指定版本为 2.6.2
npm install node-fetch@2.6.2

编写 js 脚本

const fetch = require("node-fetch");
fetch("https://csdn.net")
  .then(res=>res.text())
  .then(json=>console.log(json));

运行:

node demo02.js

运行成功。

一些其他问题

禁用 SSL 验证

处于一些原因,我的请求中需要禁用 SSL 验证,则需要安装 https 模块,并将脚本修改如下:

const fetch = require("node-fetch");
const https = require("https");
const httpsAgent = new https.Agent({rejectUnauthorized: false});
fetch("https://csdn.net",{
	agent: httpsAgent
	})
  .then(res=>res.text())
  .then(json=>console.log(json));

参考Node-fetch: Disable SSL verification

使用 await 等待请求

参考javaScript中的await和Promise

查看node-fetch 版本

PS C:\Users\ghimi\> npm view node-fetch

node-fetch@3.2.10 | MIT | deps: 3 | versions: 86
A light-weight module that brings Fetch API to node.js
https://github.com/node-fetch/node-fetch

keywords: fetch, http, promise, request, curl, wget, xhr, whatwg

dist
.tarball: https://registry.npmmirror.com/node-fetch/-/node-fetch-3.2.10.tgz
.shasum: e8347f94b54ae18b57c9c049ef641cef398a85c8
.integrity: sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==
.unpackedSize: 106.7 kB

dependencies:
data-uri-to-buffer: ^4.0.0 fetch-blob: ^3.1.4         formdata-polyfill: ^4.0.10

maintainers:
- timothygu <timothygu99@gmail.com>
- bitinn <bitinn@gmail.com>
- endless <jimmy@warting.se>
- akepinski <npm@kepinski.ch>
- node-fetch-bot <jimmy+node-fetch@warting.se>

dist-tags:
beta: 4.0.0-beta.4   cjs: 2.6.7           latest: 3.2.10       next: 3.0.0-beta.10

published 2 months ago by node-fetch-bot <jimmy+node-fetch@warting.se>

总结

出现问题的原因是自己学艺不精,没有及时认清到 AMD 和 CMD 的区别,对于 node-fetch,认识也不够深刻,但是整个过程下来解决问题的过程还是很满意的。最终也找到了问题的原因,接下来的一步就是继续研究下 ES5 和 ES6 之间在 nodejs 上的区别了。从整体来看这篇博客技术含量并不是很高,但希望能够帮助和我遇到同样问题的同学,也欢迎大佬前来指教。

参考资料

nodejs 中如何使用fetch
nodejs package.json详解
七天学会NodeJS
Uncaught SyntaxError: Cannot use import statement outside a module的解决方法
CommonJS包规范与NodeJS的包管理工具NPM
node新版本对ES Module的支持与注意事项
Uncaught SyntaxError: Cannot use import statement outside a module
Fetch API 教程
解决node环境下SyntaxError: Cannot use import statement outside a module的问题
使用 Fetch
NodeJs:“require” 函数详解,懂这个你就懂NodeJs了
Nodejs的模块机制及require用法
AMD、CMD、CommonJs、ES6的对比
Node.js v16.9.0 文档
NodeJs的CommonJS模块规范
nodejs使用fetch抓取geojson

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐