前言
- 在手写实现 Promise 并使用 promises-aplus-tests 测试是否满足 PromiseA+ 规范;之前在使用时,都是能正常工作的,由于修改了一些代码,想重新测试一下,结果执行
npm run test时,报错了。
报错信息
TypeError: adapter.deferred is not a function。
var d = adapter.deferred();
TypeError: adapter.deferred is not a function
查找错误原因
查找 promises-aplus-tests 官方文档
- 文档中有这样一段描述:
In order to test your promise library, you must expose a very minimal adapter interface. These are written as Node.js modules with a few well-known exports:
- resolved(value): creates a promise that is resolved with value.
- rejected(reason): creates a promise that is already rejected with reason.
- deferred(): creates an object consisting of { promise, resolve, reject }:
- promise is a promise that is currently in the pending state.
- resolve(value) resolves the promise with value.
- reject(reason) moves the promise from the pending state to the rejected state, with rejection reason reason.
- 意思是:
- 为了测试
promise库,需要提供一个adapter的接口; - 剩下的是说明
adapter接口导出三个方法:resolved(value):创建一个resolved(value)的promise。rejected(reason):创建一个resolved(value)的promise。deferred():deferred函数需要返回一个对象,包括(promise,resolve,reject)。
- 为了测试
- 查看
MyPromise.js文件是否有deferred方法,在文件最后,找到了是存在deferred方法定义的。
// defered
MyPromise.deferred = function () {
const dfd = {};
dfd.promise = new MyPromise( (resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
- 那为什么还是不行呢?
查看 promises-aplus-tests 源码
找到报错文件
- 报错文件位于
/node_modules/promises-aplus-tests/lib/programmaticRunner.js,报错行数为该文件第13行。
function normalizeAdapter(adapter) {
if (!adapter.resolved) { // adapter 是否有 resolved 方法
// 添加 adapter.deferred() 返回的 resolve 方法
adapter.resolved = function (value) {
var d = adapter.deferred(); // 文件第 13 行
d.resolve(value);
return d.promise;
};
}
if (!adapter.rejected) { // adapter 是否有 rejected 方法
// 添加 adapter.deferred() 返回的 reject 方法
adapter.rejected = function (reason) {
var d = adapter.deferred();
d.reject(reason);
return d.promise;
};
}
}
- 这行代码位于一个叫
normalizeAdapter的函数中,接收一个adapter,目的是把adapter标准化,添加必须的三个方法。那我们在函数的开头添加一行打印,看一下接收的adapter是什么。 - 打印出来的
adapter是一个空对象?那我们继续看normalizeAdapter函数在那些地方使用过,通过搜索函数名,发现在两个地方使用过:module.exports中
module.exports.mocha = function (adapter, mochaOpts, cb) { ... normalizeAdapter(adapter); ... }module.exports.mocha中
module.exports.mocha = function (adapter) { normalizeAdapter(adapter); ... }
查找引入 programmaticRunner.js 的文件
- 想要查找引入
programmaticRunner.js的文件,我们可以通过在左侧promises-aplus-tests文件夹下直接搜索文件名;由于promises-aplus-tests下的文件不多,也可以一个个文件打开去文件开头找。 - 找到文件在同级的
cli.js文件中引入并使用。
// promises-aplus-tests/lib/cli.js
#!/usr/bin/env node
"use strict";
var path = require("path");
var getMochaOpts = require("./getMochaOpts");
var programmaticRunner = require("./programmaticRunner");
var filePath = getAdapterFilePath();
var adapter = adapterObjectFromFilePath(filePath);
var mochaOpts = getMochaOpts(process.argv.slice(3));
programmaticRunner(adapter, mochaOpts, function (err) {
if (err) {
process.exit(err.failures || -1);
}
});
// 获取需要测试文件的绝对路径
function getAdapterFilePath() {
if (process.argv[2]) {
return path.join(process.cwd(), process.argv[2]);
} else {
throw new Error("Specify your adapter file as an argument.");
}
}
// 通过文件的绝对路径使用 require 引入文件
function adapterObjectFromFilePath(filePath) {
try {
return require(filePath);
} catch (e) {
var error = new Error("Error `require`ing adapter file " + filePath + "\n\n" + e);
error.cause = e;
throw error;
}
}
- 从
cli.js文件中可以找到adapter是通过adapterObjectFromFilePath函数返回的,adapterObjectFromFilePath函数又接收了一个filePath的参数,根据参数名称我们大概能猜出这是一个文件的路径;filePath是通过getAdapterFilePath函数返回的,那我们就先查看getAdapterFilePath函数。
function getAdapterFilePath() {
if (process.argv[2]) {
return path.join(process.cwd(), process.argv[2]);
} else {
throw new Error("Specify your adapter file as an argument.");
}
}
getAdapterFilePath通过判断获取命令参数判断是否有第二个参数(需要测试的文件名称),存在就返回当前项目目录的绝对地址拼接需要测试的文件名称的完整路径,不存在就抛出一个错误。
// package.json
"scripts": {
"test": "promises-aplus-tests MyPromise"
},
adapterObjectFromFilePath通过getAdapterFilePath返回的文件路径使用require方式引入需要测试的文件;不存在就抛出异常。
function adapterObjectFromFilePath(filePath) {
try {
return require(filePath); // 使用的 CommonJS 规范
} catch (e) {
var error = new Error("Error `require`ing adapter file " + filePath + "\n\n" + e);
error.cause = e;
throw error;
}
}
- 到这儿,我们就知道为什么我们拿到的是一个空对象了,因为
MyPromise.js文件中没有使用CommonJS规范去暴露我们的MyPromise对象,所以我们只需要添加一下代码在文件最后即可。
module.exports = MyPromise
- 再次执行
npm run test,就可以正常运行了。 - 解决方法是不是很简单,就这?就这。
总结
- 当遇到某些问题,他人没有遇到过时,我们可以通过梳理问题,一步步去查找问题的根源,对比是否与相应要求与自己的是否一致,追根溯源。
- 不要害怕看源码,梳理代码的执行过程,查看每一步的执行结果,看执行结果是否满足下一步执行条件,不满足再往上查找是哪一步结果出了问题。
往期精彩
- 从0搭建Vite+Vue3+Element-Plus+Vue-Router+ESLint+husky+lint-staged
- 「前端进阶」JavaScript手写方法/使用技巧自查
- JavaScript设计模式之简介及创建型模式
- 公众号打开小程序最佳解决方案(Vue)
- Axios你可能不知道使用方式
「点赞、收藏和评论」
❤️关注+点赞+收藏+评论+转发❤️,创作不易,鼓励笔者创作更好的文章,谢谢🙏大家。