JavaScript函数全面解析:从基础到高阶应用
1. JavaScript函数:从基础到实战的全面解析
在JavaScript的世界里,函数就像瑞士军刀一样万能。它们不仅是代码复用的基本单元,更是构建复杂应用的基石。我见过太多初学者因为对函数理解不够深入而陷入各种"坑"里——从变量作用域的困惑到闭包的滥用,从回调地狱到异步处理的混乱。这篇文章将带你系统梳理JavaScript函数的方方面面,从最基础的声明方式到高阶函数应用,再到实际开发中的最佳实践。
函数在JavaScript中是一等公民,这意味着它们可以像其他数据类型一样被赋值、传递和返回。这种特性赋予了JavaScript极大的灵活性,但也带来了不少"陷阱"。比如,函数声明提升(hoisting)可能导致代码执行顺序与书写顺序不一致;箭头函数与普通函数的this绑定机制完全不同;闭包如果不当使用可能导致内存泄漏...这些问题在实际项目中我都遇到过,也总结出了一套行之有效的应对方案。
2. 函数基础:声明与调用
2.1 四种函数定义方式对比
JavaScript提供了多种定义函数的方式,每种都有其适用场景:
- 函数声明(最传统的方式):
function greet(name) { return `Hello, ${name}!`; }特点:存在函数提升(hoisting),可以在定义前调用
- 函数表达式(更灵活的方式):
const greet = function(name) { return `Hello, ${name}!`; };特点:不存在提升,适合需要条件性定义的场景
- 箭头函数(ES6新增,简洁的语法):
const greet = (name) => `Hello, ${name}!`;特点:没有自己的this、arguments、super或new.target,适合回调函数
- Function构造函数(极少使用):
const greet = new Function('name', 'return `Hello, ${name}!`');特点:动态生成函数,但存在安全性和性能问题
提示:在大多数情况下,优先使用函数声明或箭头函数。Function构造函数除非有特殊需求,否则不建议使用。
2.2 参数传递的细节与技巧
JavaScript函数的参数处理非常灵活,但也容易引发问题:
function logParams(a, b, c) { console.log(a, b, c); } logParams(1); // 1 undefined undefined logParams(1, 2, 3, 4); // 1 2 3 (多余的参数被忽略)默认参数(ES6):
function createUser(name, role = 'user') { return { name, role }; }剩余参数(...操作符):
function sum(...numbers) { return numbers.reduce((total, num) => total + num, 0); }参数解构:
function drawChart({ width = 100, height = 100, type = 'line' }) { console.log(`Drawing ${type} chart (${width}x${height})`); }3. 函数进阶:作用域与闭包
3.1 作用域链与变量提升
JavaScript的作用域规则常常让初学者困惑。看这个例子:
var x = 1; function outer() { var y = 2; function inner() { var z = 3; console.log(x + y + z); // 6 } inner(); } outer();关键点:
- 函数作用域:由函数定义的位置决定
- 变量提升:var声明的变量会提升到函数顶部
- let/const:块级作用域,不存在提升(TDZ暂时性死区)
3.2 闭包的实际应用
闭包是JavaScript中最强大的特性之一,也是面试必问的点:
function createCounter() { let count = 0; return { increment() { count++; }, getCount() { return count; } }; } const counter = createCounter(); counter.increment(); console.log(counter.getCount()); // 1实际应用场景:
- 模块模式(封装私有变量)
- 函数工厂(动态生成函数)
- 记忆化(缓存计算结果)
- 事件处理(保持状态)
注意:不当使用闭包会导致内存泄漏,特别是DOM事件处理中引用DOM元素时。
4. 高阶函数与函数式编程
4.1 数组的高阶函数方法
JavaScript数组提供了丰富的高阶函数方法:
const numbers = [1, 2, 3, 4, 5]; // map: 转换数组 const doubled = numbers.map(n => n * 2); // filter: 筛选元素 const evens = numbers.filter(n => n % 2 === 0); // reduce: 累积计算 const sum = numbers.reduce((acc, n) => acc + n, 0); // find: 查找元素 const firstEven = numbers.find(n => n % 2 === 0);4.2 函数组合与柯里化
函数组合:
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x); const add5 = x => x + 5; const multiply3 = x => x * 3; const transform = compose(multiply3, add5); console.log(transform(2)); // (2 + 5) * 3 = 21柯里化:
const curry = fn => { const arity = fn.length; return function curried(...args) { if (args.length >= arity) return fn(...args); return (...moreArgs) => curried(...args, ...moreArgs); }; }; const add = (a, b, c) => a + b + c; const curriedAdd = curry(add); console.log(curriedAdd(1)(2)(3)); // 6 console.log(curriedAdd(1, 2)(3)); // 65. 异步函数与错误处理
5.1 从回调到async/await的演进
JavaScript异步编程经历了几个阶段:
- 回调地狱:
getData(function(a) { getMoreData(a, function(b) { getMoreData(b, function(c) { // 深度嵌套难以维护 }); }); });- Promise链:
getData() .then(a => getMoreData(a)) .then(b => getMoreData(b)) .then(c => { // 更线性的流程 }) .catch(err => console.error(err));- async/await(现代最佳实践):
async function processData() { try { const a = await getData(); const b = await getMoreData(a); const c = await getMoreData(b); // 代码像同步一样清晰 } catch (err) { console.error(err); } }5.2 错误处理的最佳实践
JavaScript错误处理有几个关键点:
- 不要忽略错误:
// 不好 async function fetchData() { try { const res = await fetch('/api'); return await res.json(); } catch { // 静默失败 } } // 好 async function fetchData() { try { const res = await fetch('/api'); if (!res.ok) throw new Error('Network response was not ok'); return await res.json(); } catch (err) { console.error('Fetch failed:', err); // 返回默认值或重新抛出 return { data: [] }; } }- 自定义错误类型:
class ApiError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; this.name = 'ApiError'; } } async function fetchUser(id) { const res = await fetch(`/users/${id}`); if (!res.ok) { throw new ApiError('User not found', 404); } return await res.json(); }6. 性能优化与调试技巧
6.1 函数性能优化
- 避免不必要的函数创建:
// 不好:每次渲染都创建新函数 function Component() { return <button onClick={() => console.log('Clicked')}>Click</button>; } // 好:使用useCallback或类方法 function Component() { const handleClick = useCallback(() => console.log('Clicked'), []); return <button onClick={handleClick}>Click</button>; }- 节流与防抖:
// 防抖:最后一次调用后等待一段时间执行 function debounce(fn, delay) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } // 节流:固定时间间隔执行一次 function throttle(fn, interval) { let lastTime = 0; return function(...args) { const now = Date.now(); if (now - lastTime >= interval) { fn.apply(this, args); lastTime = now; } }; }6.2 调试技巧
- console的高级用法:
console.table([ { name: 'Alice', age: 25 }, { name: 'Bob', age: 30 } ]); console.time('fetch'); await fetch('/api'); console.timeEnd('fetch'); // 输出请求耗时- 利用debugger语句:
function complexCalculation(data) { debugger; // 执行到这里会暂停 // 复杂计算逻辑 }- 使用函数名辅助调试:
// 匿名函数难以追踪 setTimeout(function() { // ... }, 1000); // 命名函数更好 setTimeout function fetchData() { // ... }, 1000);7. 实战练习与常见面试题
7.1 经典函数练习题
- 实现一个memoize函数:
function memoize(fn) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result = fn.apply(this, args); cache.set(key, result); return result; }; } const factorial = memoize(n => { if (n <= 1) return 1; return n * factorial(n - 1); });- 实现一个pipe函数:
function pipe(...fns) { return function(x) { return fns.reduce((v, f) => f(v), x); }; } const add1 = x => x + 1; const double = x => x * 2; const square = x => x * x; const transform = pipe(add1, double, square); console.log(transform(2)); // ((2 + 1) * 2)^2 = 367.2 常见面试题解析
- this绑定问题:
const obj = { name: 'Alice', greet: function() { console.log(`Hello, ${this.name}`); }, greetArrow: () => { console.log(`Hello, ${this.name}`); } }; obj.greet(); // "Hello, Alice" obj.greetArrow(); // "Hello, undefined" (箭头函数没有自己的this)- 闭包与循环变量:
for (var i = 0; i < 3; i++) { setTimeout(function() { console.log(i); // 输出3次3(var没有块作用域) }, 100); } // 解决方案1:使用let for (let i = 0; i < 3; i++) { setTimeout(function() { console.log(i); // 输出0,1,2 }, 100); } // 解决方案2:IIFE for (var i = 0; i < 3; i++) { (function(j) { setTimeout(function() { console.log(j); // 输出0,1,2 }, 100); })(i); }8. 现代JavaScript函数特性
8.1 可选链与空值合并
ES2020引入的两个实用特性:
const user = { profile: { name: 'Alice', address: { city: 'New York' } } }; // 传统方式 const city = user && user.profile && user.profile.address && user.profile.address.city; // 可选链 const city = user?.profile?.address?.city; // 空值合并 const age = user?.profile?.age ?? 18; // 如果age是null/undefined则使用188.2 私有类字段与方法
ES2022正式将私有字段和方法纳入标准:
class Counter { #count = 0; // 私有字段 #increment() { // 私有方法 this.#count++; } tick() { this.#increment(); return this.#count; } } const counter = new Counter(); console.log(counter.tick()); // 1 console.log(counter.#count); // 报错:私有字段无法外部访问9. 函数设计原则与最佳实践
9.1 SOLID原则在函数设计中的应用
- 单一职责原则:
// 不好:函数做太多事情 function processUser(user) { validateUser(user); saveToDatabase(user); sendWelcomeEmail(user); logUserActivity(user); } // 好:拆分职责 function processUser(user) { validateUser(user); persistUser(user); notifyUser(user); } function persistUser(user) { saveToDatabase(user); logUserActivity(user); } function notifyUser(user) { sendWelcomeEmail(user); }- 开闭原则:
// 不好:直接修改函数 function calculateArea(shape) { if (shape.type === 'circle') return Math.PI * shape.radius ** 2; if (shape.type === 'square') return shape.side ** 2; // 添加新形状需要修改函数 } // 好:使用多态 class Shape { area() { throw new Error('Method not implemented'); } } class Circle extends Shape { constructor(radius) { super(); this.radius = radius; } area() { return Math.PI * this.radius ** 2; } } // 添加新形状只需扩展类,不修改现有代码9.2 函数命名的艺术
好的函数名应该:
- 明确表达意图
- 使用动词开头
- 避免模糊的词语如"handle", "process"
- 保持一致的命名风格
// 不好 function doStuff(data) { /* ... */ } function stuffHandler() { /* ... */ } // 好 function validateUserInput(input) { /* ... */ } function calculateMonthlyRevenue(orders) { /* ... */ } function formatDateForDisplay(date) { /* ... */ }10. TypeScript中的函数增强
10.1 函数类型注解
TypeScript为JavaScript函数添加了强大的类型系统:
// 函数类型表达式 type GreetFunction = (name: string) => string; // 可选参数与默认值 function createUser( name: string, age?: number, role: string = 'user' ): { name: string; age?: number; role: string } { return { name, age, role }; } // 函数重载 function getData(id: number): DataItem; function getData(query: string): DataItem[]; function getData(param: number | string): DataItem | DataItem[] { // 实现 }10.2 泛型函数
function identity<T>(arg: T): T { return arg; } const output = identity<string>("hello"); // 类型为string // 泛型约束 function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; } const user = { name: 'Alice', age: 25 }; const name = getProperty(user, 'name'); // string const age = getProperty(user, 'age'); // number11. 函数式编程库实战
11.1 Lodash函数工具
Lodash提供了大量实用的函数工具:
import _ from 'lodash'; // 函数节流 const throttledScroll = _.throttle(updatePosition, 100); window.addEventListener('scroll', throttledScroll); // 深度克隆 const deepCopy = _.cloneDeep(originalObject); // 函数组合 const transform = _.flow([ filterActiveUsers, sortByAge, takeFirst10, formatForDisplay ]);11.2 Ramda的纯函数风格
Ramda强调纯函数和不变性:
import R from 'ramda'; const users = [ { name: 'Alice', age: 25 }, { name: 'Bob', age: 30 } ]; // 函数组合 const getAdultNames = R.pipe( R.filter(user => user.age >= 18), R.map(user => user.name), R.sortBy(R.identity) ); // 柯里化 const add = R.curry((a, b) => a + b); const add5 = add(5); console.log(add5(3)); // 812. 浏览器API中的函数应用
12.1 DOM事件处理
// 传统方式 button.addEventListener('click', function(event) { console.log('Button clicked', this); // this指向button元素 }); // 箭头函数 button.addEventListener('click', (event) => { console.log('Button clicked', this); // this继承自外层作用域 }); // 事件委托 document.getElementById('list').addEventListener('click', function(event) { if (event.target.matches('li.item')) { console.log('Item clicked:', event.target.textContent); } });12.2 Web Workers中的函数
// main.js const worker = new Worker('worker.js'); worker.postMessage({ command: 'calculate', data: 100 }); worker.onmessage = function(event) { console.log('Result:', event.data); }; // worker.js self.onmessage = function(event) { if (event.data.command === 'calculate') { const result = heavyCalculation(event.data.data); self.postMessage(result); } }; function heavyCalculation(n) { // 耗时的计算 return n * 2; }13. Node.js中的函数特色
13.1 错误优先回调
Node.js传统的异步模式:
const fs = require('fs'); function readFilePromise(path) { return new Promise((resolve, reject) => { fs.readFile(path, 'utf8', (err, data) => { if (err) return reject(err); resolve(data); }); }); } // 现代用法 async function processFile(path) { try { const content = await readFilePromise(path); console.log(content); } catch (err) { console.error('Error reading file:', err); } }13.2 流处理中的函数
const { createReadStream } = require('fs'); const { pipeline } = require('stream'); // 传统方式 createReadStream('input.txt') .on('data', chunk => console.log('Chunk:', chunk)) .on('end', () => console.log('Done')) .on('error', err => console.error('Error:', err)); // 使用pipeline(推荐) pipeline( createReadStream('input.txt'), transformStream, writeStream, err => { if (err) console.error('Pipeline failed:', err); else console.log('Pipeline succeeded'); } );14. 函数测试与调试
14.1 单元测试实践
使用Jest测试函数:
// math.js function sum(a, b) { if (typeof a !== 'number' || typeof b !== 'number') { throw new TypeError('Arguments must be numbers'); } return a + b; } // math.test.js describe('sum function', () => { test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); test('throws error with non-number args', () => { expect(() => sum('1', 2)).toThrow(TypeError); }); });14.2 函数性能测试
function testPerformance(fn, iterations = 10000) { const start = performance.now(); for (let i = 0; i < iterations; i++) { fn(); } const end = performance.now(); return end - start; } // 比较两种实现 const time1 = testPerformance(() => oldImplementation()); const time2 = testPerformance(() => newImplementation()); console.log(`Improvement: ${((time1 - time2) / time1 * 100).toFixed(2)}%`);15. 函数安全注意事项
15.1 避免eval与Function构造函数
// 危险:可能执行恶意代码 const userInput = 'alert("XSS")'; eval(userInput); // 安全替代方案 const operations = { add: (a, b) => a + b, subtract: (a, b) => a - b }; function safeEval(operation, a, b) { if (operations[operation]) { return operations[operation](a, b); } throw new Error('Invalid operation'); }15.2 防止原型污染
// 危险:可能修改原型 function merge(target, source) { for (const key in source) { target[key] = source[key]; // 可能覆盖原型方法 } return target; } // 安全方式 function safeMerge(target, source) { return Object.assign({}, target, source); // 或使用展开运算符 // return { ...target, ...source }; }16. 函数与内存管理
16.1 闭包内存泄漏
// 可能导致内存泄漏 function setupHeavyOperation() { const largeData = new Array(1000000).fill('data'); return function() { // 使用largeData console.log(largeData.length); }; } const operation = setupHeavyOperation(); // 即使不再需要,largeData仍被保留 // 解决方案:在不需要时手动解除引用 operation = null; // 允许垃圾回收16.2 WeakMap与WeakSet的应用
// 使用WeakMap存储私有数据 const privateData = new WeakMap(); class User { constructor(name) { privateData.set(this, { name }); } getName() { return privateData.get(this).name; } } // 当User实例被垃圾回收时,对应的私有数据也会被自动清除17. 函数式React组件
17.1 Hooks中的函数
import React, { useState, useEffect, useCallback } from 'react'; function Counter() { const [count, setCount] = useState(0); // 使用useCallback避免不必要的重新创建 const increment = useCallback(() => { setCount(c => c + 1); }, []); useEffect(() => { const timer = setInterval(increment, 1000); return () => clearInterval(timer); }, [increment]); return <div>Count: {count}</div>; }17.2 自定义Hook
function useLocalStorage(key, initialValue) { const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.error(error); return initialValue; } }); const setValue = useCallback((value) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.error(error); } }, [key, storedValue]); return [storedValue, setValue]; } // 使用 const [name, setName] = useLocalStorage('username', 'Guest');18. 函数与设计模式
18.1 工厂模式
function createUser(type) { switch (type) { case 'admin': return createAdmin(); case 'member': return createMember(); default: throw new Error('Invalid user type'); } } function createAdmin() { const permissions = ['read', 'write', 'delete']; return { permissions, can(action) { return this.permissions.includes(action); } }; }18.2 策略模式
const paymentStrategies = { creditCard(amount) { console.log(`Processing $${amount} via Credit Card`); // 实际处理逻辑 }, paypal(amount) { console.log(`Processing $${amount} via PayPal`); // 实际处理逻辑 }, crypto(amount) { console.log(`Processing $${amount} via Crypto`); // 实际处理逻辑 } }; function processPayment(method, amount) { if (paymentStrategies[method]) { return paymentStrategies[method](amount); } throw new Error('Invalid payment method'); }19. 函数与算法
19.1 递归函数优化
// 普通递归(可能栈溢出) function factorial(n) { if (n <= 1) return 1; return n * factorial(n - 1); } // 尾递归优化(ES6严格模式下) function factorial(n, acc = 1) { if (n <= 1) return acc; return factorial(n - 1, n * acc); } // 使用Trampoline处理大数 function trampoline(fn) { return function(...args) { let result = fn(...args); while (typeof result === 'function') { result = result(); } return result; }; } const factorial = trampoline(function(n, acc = 1) { if (n <= 1) return acc; return () => factorial(n - 1, n * acc); });19.2 分治算法实现
function quickSort(arr) { if (arr.length <= 1) return arr; const pivot = arr[0]; const left = []; const right = []; for (let i = 1; i < arr.length; i++) { if (arr[i] < pivot) { left.push(arr[i]); } else { right.push(arr[i]); } } return [...quickSort(left), pivot, ...quickSort(right)]; }20. 函数与数据结构
20.1 实现链表操作
class ListNode { constructor(value, next = null) { this.value = value; this.next = next; } } function createList(values) { let head = null; let current = null; for (const value of values) { const node = new ListNode(value); if (!head) { head = node; current = node; } else { current.next = node; current = node; } } return head; } function reverseList(head) { let prev = null; let current = head; while (current) { const next = current.next; current.next = prev; prev = current; current = next; } return prev; }20.2 树遍历函数
class TreeNode { constructor(value, left = null, right = null) { this.value = value; this.left = left; this.right = right; } } // 前序遍历 function preOrder(node, visit) { if (!node) return; visit(node.value); preOrder(node.left, visit); preOrder(node.right, visit); } // 中序遍历 function inOrder(node, visit) { if (!node) return; inOrder(node.left, visit); visit(node.value); inOrder(node.right, visit); } // 后序遍历 function postOrder(node, visit) { if (!node) return; postOrder(node.left, visit); postOrder(node.right, visit); visit(node.value); }21. 函数与正则表达式
21.1 高阶正则函数
function createMatcher(pattern) { const regex = new RegExp(pattern); return function(str) { const result = regex.exec(str); if (!result) return null; return { fullMatch: result[0], groups: result.slice(1), index: result.index, input: result.input }; }; } const matchEmail = createMatcher('^([^@]+)@([^@]+)$'); const emailInfo = matchEmail('user@example.com');21.2 正则替换函数
function createReplacer(pattern, replacement) { const regex = new RegExp(pattern, 'g'); return function(str) { return str.replace(regex, replacement); }; } const redactPhone = createReplacer( /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, '[PHONE REDACTED]' ); const safeText = redactPhone('Call me at 555-123-4567 or 5559876543');22. 函数与日期处理
22.1 日期格式化函数
function formatDate(date, format = 'YYYY-MM-DD') { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return format .replace('YYYY', year) .replace('MM', month) .replace('DD', day) .replace('HH', hours) .replace('mm', minutes) .replace('ss', seconds); } console.log(formatDate(new Date(), 'YYYY/MM/DD HH:mm:ss'));22.2 日期计算函数
function addDays(date, days) { const result = new Date(date); result.setDate(result.getDate() + days); return result; } function businessDaysBetween(start, end) { let count = 0; const current = new Date(start); while (current <= end) { const day = current.getDay(); if (day !== 0 && day !== 6) { count++; } current.setDate(current.getDate() + 1); } return count; }23. 函数与数学计算
23.1 数值处理函数
function clamp(value, min, max) { return Math.min(Math.max(value, min), max); } function roundToPrecision(value, precision = 2) { const factor = 10 ** precision; return Math.round(value * factor) / factor; } function isPrime(n) { if (n <= 1) return false; if (n <= 3) return true; if (n % 2 === 0 || n % 3 === 0) return false; for (let i = 5; i * i <= n; i += 6) { if (n % i === 0 || n % (i + 2) === 0) return false; } return true; }23.2 几何计算函数
function calculateDistance(p1, p2) { const dx = p2.x - p1.x; const dy = p2.y - p1.y; return Math.sqrt(dx * dx + dy * dy); } function isPointInCircle(point, circle) { const distance = calculateDistance(point, circle.center); return distance <= circle.radius; } function lineIntersection(line1, line2) { // 解线性方程组 const denominator = (line1.p2.y - line1.p1.y) * (line2.p2.x - line2.p1.x) - (line1.p2.x - line1.p1.x) * (line2.p2.y - line2.p1.y); if (denominator === 0) return null; // 平行或重合 const ua = ((line1.p2.x - line1.p1.x) * (line2.p1.y - line1.p1.y) - (line1.p2.y - line1.p1.y) * (line2.p1.x - line1.p1.x)) / denominator; const ub = ((line2.p2.x - line2.p1.x) * (line2.p1.y - line1.p1.y) - (line2.p2.y - line2.p1.y) * (line2.p1.x - line1.p1.x)) / denominator; if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return null; // 交点不在线段上 return { x: line2.p1.x + ua * (line2.p2.x - line2.p1.x), y: line2.p1.y + ua * (line2.p2.y - line2.p1.y) }; }24. 函数与文件处理
24.1 文件读取函数
const fs = require('fs').promises; async function readFilesSequentially(filePaths) { const results = []; for (const path of filePaths) { try { const content = await fs.readFile(path, 'utf8'); results.push({ path, content, status: 'success' }); } catch (err) { results.push({ path, error: err.message, status: 'failed' }); } } return results; } async function readFilesParallel(filePaths) { const promises = filePaths.map(async path => { try { const content = await fs.readFile(path, 'utf8'); return { path, content, status: 'success' }; } catch (err) { return { path, error: err.message, status: 'failed' }; } }); return Promise.all(promises); }24.2 文件转换函数
const csv = require('csv-parser'); const fs = require('fs'); function csvToJson(csvPath, jsonPath) { return new Promise((resolve, reject) => { const results = []; fs.createReadStream(csvPath) .pipe(csv()) .on('data', data => results.push(data)) .on('end', () => { fs.writeFile(jsonPath, JSON.stringify(results, null, 2), err => { if (err) return reject(err); resolve(results.length); }); }) .on('error', reject); }); }