普通视图

发现新文章,点击刷新页面。
昨天 — 2025年6月9日首页

JS 模块化

作者 古夕
2025年6月8日 00:32

一、JS 中的模块化规范

要明白我们的打包工具究竟做了什么,首先必须明白的一点就是 JS 中的模块化。
在 ES6 规范之前,我们有 CommonJS、AMD 等主流的模块化规范。

1、CommonJS(node.js 原生 & 同步加载 & 多次加载一次执行)

Node.js 是一个基于 V8 引擎,事件驱动 I/O 的服务端 JS 运行环境。
在 2009 年刚推出时,它就实现了一套名为 CommonJS 的模块化规范。

在 CommonJS 规范里:

  • 每个 JS 文件都是一个模块(module),每个模块内部都可以使用 require 函数和 module.exports 对象,来对模块进行导入和导出。

示例代码

// index.js
require('./moduleA');
const str = require('./moduleB');
console.log('index', str);

// moduleA.js
const timestamp = require('./moduleB');
setTimeout(() => console.log('moduleA', timestamp), 1000);

// moduleB.js
module.exports = new Date().getTime();

执行说明

  • index.js 代表的模块通过执行 require 函数,分别加载了相对路径为 ./moduleA./moduleB 的两个模块,同时输出 moduleB 模块的结果。
  • moduleA.js 文件内也通过 require 函数加载了 moduleB.js 模块,在 1s 后也输出了加载进来的结果。
  • moduleB.js 文件内部定义了一个时间戳,使用 module.exports 对象导出。

运行结果(执行 node index.js

index 1738743973675
moduleA 1738743973675

模块特性

  • 每个模块都是单例的,当一个模块被多次加载的时候,最终执行实际上只会被执行一次。

2、AMD(浏览器端 & 异步 & 多次加载一次执行)

另一个为 WEB 开发者所熟知的 JS 运行环境就是浏览器了。浏览器并没有提供像 Node.js 里一样的 require 方法。
不过,受到 CommonJS 模块化规范的启发,WEB 端还是逐渐发展起来了 AMD、SystemJS 规范等适合浏览器端运行的 JS 模块化开发规范。

AMD 全称 Asynchronous module definition,意为异步的模块定义,不同于 CommonJS 规范的同步加载,AMD 正如其名所有模块默认都是异步加载,这也是早期为了满足 web 开发的需要,因为如果在 web 端也使用同步加载,那么页面在解析脚本文件的过程中可能使页面暂停响应。

示例代码

// index.js
require(['moduleA', 'moduleB'], function(moduleA, moduleB) {
    console.log('index', moduleB);
});

// moduleA.js
define(function(require) {
    const timestamp = require('moduleB');
    setTimeout(() => console.log('moduleA', timestamp), 1000);
});

// moduleB.js
define(function(require) {
    return new Date().getTime();
});

使用说明
如果想要使用 AMD 规范,我们还需要添加一个符合 AMD 规范的加载器脚本在页面中,符合 AMD 规范实现的库很多,比较有名的就是 require.js

3、ESModule

前面我们说到的 CommonJS 规范和 AMD 规范有这么几个特点:

  1. 语言上层的运行环境实现的模块化规范,模块化规范由环境自己定义。
  2. 相互之间不能兼容使用。例如不能在 Node.js 运行 AMD 模块,不能直接在浏览器运行 CommonJS 模块。

在 EcmaScript 2015(也就是我们常说的 ES6)之后,JS 有了语言层面的模块化导入导出语法以及与之匹配的 ESModule 规范。
使用 ESModule 规范,我们可以通过 importexport 两个关键词来对模块进行导入与导出。

示例代码(基于 ESModule 规范改写之前的例子)

// index.js
import './moduleA';
import str from './moduleB';
console.log(str);

// moduleA.js
import timestamp from './moduleB';
setTimeout(() => console.log('moduleA', timestamp), 1000);

// moduleB.js
export default new Date().getTime();

Webpack 配置(webpack.config.js

const path = require('path');

module.exports = {
    mode: 'none',
    entry: './index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.bundle.js', // 注意 filename 是小写不是 fileNane
        publicPath: 'dist/'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader', // 所有的 js 文件进 webpack 处理的过程中都使用 babel 进行编译
                    options: {
                        presets: ['@babel/preset-env'] // 支持使用环境参数来处理不同的内容
                    }
                }
            }
        ]
    }
};

依赖安装与运行

# 安装依赖
yarn add webpack webpack-cli @babel/core babel-loader @babel/preset-env 

# 编译打包
./node_modules/.bin/webpack 

# 启动静态服务器(可使用 Python 简单启动)
python3 -m http.server 

关于 JS 运行环境与解析器

每个 JS 的运行环境都有一个解析器,否则这个环境也不会认识 JS 语法。
它的作用就是用 ECMAScript 的规范去解释 JS 语法,也就是处理和执行语言本身的内容。
例如按照逻辑正确执行 var a = "123"; function func() { console.log("hahaha"); } 之类的内容。

在解析器的上层,每个运行环境都会在解释器的基础上封装一些环境相关的 API。
例如 Node.js 中的 global 对象、process 对象,浏览器中的 window 对象、document 对象等等。

这些运行环境的 API 规范各自规范的影响,例如浏览器端的 W3C 规范,它们规定了 window 对象和 document 对象上的 API 内容,以使得我们能让 document.getElementById 这样的 API 在所有浏览器上运行正常。

以上就是对 JS 模块化相关规范(CommonJS、AMD、ESModule )以及 JS 运行环境、解析器等相关内容的梳理 ,通过不同规范的对比,能更清晰理解在不同场景(Node.js 服务端、浏览器端等 )下模块化的实现与应用 。

如何将异步操作封装为Promise

作者 古夕
2025年6月8日 00:17

一、传统回调函数与Promise封装对比

1. 传统回调函数示例

// 原始异步函数(带回调)
function dynamicFunc(cb) {
    setTimeout(() => {
        console.log('1s 后显示');
        cb();
    }, 1000);
}

const callback = () => {
    console.log('在异步结束后 log');
}

// 调用方式:传入回调函数
dynamicFunc(callback);

2. 封装为Promise后的版本

// 封装为Promise的异步函数
function dynamicFuncAsync() {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log('1s 后显示');
            resolve();
        }, 1000);
    });
}

const callback = () => {
    console.log('在异步结束后 log');
}

// 调用方式:链式调用then
dynamicFuncAsync().then(callback);

二、AJAX请求的Promise封装实践

1. 传统AJAX回调写法

function ajax(url, success, fail) {
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = () => {
        if (this.readyState !== 4) return;
        if (this.status === 200) {
            success(this.response);
        } else {
            fail(new Error(this.statusText));
        }
    };
    client.send();
}

// 调用方式:传入成功/失败回调
ajax('/ajax.json', 
    () => console.log('成功'), 
    () => console.log('失败')
);

2. Promise封装后的AJAX函数

function ajaxAsync(url) {
    return new Promise((resolve, reject) => {
        const client = new XMLHttpRequest();
        client.open("GET", url);
        client.onreadystatechange = () => {
            if (this.readyState !== 4) return;
            if (this.status === 200) {
                resolve(this.response);
            } else {
                reject(new Error(this.statusText));
            }
        };
        client.send();
    });
}

// 调用方式:Promise链式处理
ajaxAsync('/ajax.json')
    .then(response => console.log('成功:', response))
    .catch(error => console.log('失败:', error.message));

三、核心封装原则总结

  1. 封装步骤关键点

    • 在函数内部返回一个新的Promise实例
    • 在原异步操作完成时,根据结果调用resolvereject
    • 异步操作的参数通过resolve/reject传递给后续.then回调
  2. 优势对比

    特性 传统回调函数 Promise封装
    代码可读性 多层嵌套易形成回调地狱 链式调用更清晰
    错误处理 需要层层传递错误回调 统一通过.catch捕获
    流程控制 难以实现并行/串行 支持Promise.all/race
  3. 最佳实践建议

    • 对所有异步API(如文件操作、网络请求)进行Promise封装
    • 保持封装函数的参数简洁(如ajaxAsync(url)而非ajaxAsync(url, method, data)
    • .catch中处理全局错误,避免Promise链中断

四、高级封装技巧

  1. 处理多参数回调

    // 原函数(返回多个结果)
    function processData(data, success, fail) {
        // 异步处理data
        success(result1, result2);
    }
    
    // Promise封装
    function processDataAsync(data) {
        return new Promise((resolve, reject) => {
            processData(data, 
                (res1, res2) => resolve({ result1: res1, result2: res2 }),
                error => reject(error)
            );
        });
    }
    
    // 调用时解构参数
    processDataAsync(data)
        .then(({ result1, result2 }) => { /* 处理多结果 */ })
    
  2. 封装Node.js风格的回调函数

    // Node.js传统回调:第一个参数为错误(error-first)
    const fs = require('fs');
    
    // 封装fs.readFile
    function readFileAsync(path) {
        return new Promise((resolve, reject) => {
            fs.readFile(path, (error, data) => {
                if (error) reject(error);
                resolve(data);
            });
        });
    }
    
    // 调用方式
    readFileAsync('/data.txt')
        .then(data => console.log('文件内容:', data))
        .catch(error => console.error('读取失败:', error));
    

五、总结:Promise封装的核心价值

  1. 代码结构优化
    将"回调地狱"转化为线性的链式调用,提升代码可维护性。

  2. 错误处理标准化
    通过统一的.catch机制处理异步错误,避免传统回调中错误传递的遗漏。

  3. 异步流程增强
    支持Promise组合操作(如Promise.all并行执行多个异步任务),简化复杂异步流程控制。

通过上述封装方式,任何异步操作都能转化为Promise接口,从而充分利用ES6异步编程的优势,让代码更简洁、更健壮。

昨天以前首页

Promise 解决过程(Promise Resolution Procedure)详解

作者 古夕
2025年6月8日 00:22

Promise 解决过程是 Promise A+ 规范的核心机制,用于处理 then 回调返回值与 Promise 状态的关系。以下从规范角度结合代码示例解析这一过程。

一、解决过程的核心定义

Promise 解决过程是一个抽象操作 [[Resolve]](promise, x),表示将 promise 的状态与值根据 x 的类型和状态进行解析。其核心逻辑是:x 是 Promise 或 Thenable(具有 then 方法),则使 promise 接受 x 的状态;否则用 x 的值完成 promise

二、解决过程的具体规则

1. xpromise 为同一对象

  • 规则:若 promisex 指向同一实例,以 TypeError 拒绝 promise,避免循环引用。
  • 示例
    const promise = new Promise(resolve => {
      resolve(promise); // promise 和 x 为同一对象
    });
    
    promise.then(
      value => console.log('成功'),
      reason => console.log('错误:', reason) // 输出:错误: TypeError
    );
    

2. x 是 Promise 实例

  • 规则
    • x 处于 pendingpromise 保持 pending,直至 x 状态确定。
    • x 处于 fulfilledpromise 以相同值 fulfilled
    • x 处于 rejectedpromise 以相同原因 rejected
  • 示例
    function createPromise(delay, value) {
      return new Promise(resolve => {
        setTimeout(() => resolve(value), delay);
      });
    }
    
    const p1 = createPromise(100, 'p1 resolved');
    const p2 = new Promise(resolve => {
      resolve(p1); // x 是 Promise
    });
    
    p2.then(
      value => console.log('p2 结果:', value), // 输出:p2 结果: p1 resolved
      reason => console.log('p2 错误:', reason)
    );
    

3. x 是 Object 或 Function(Thenable)

  • 规则
    1. 尝试获取 x.then,若获取时抛出异常 e,则以 e 拒绝 promise
    2. then 是函数,调用 x.then(resolvePromise, rejectPromise),其中:
      • resolvePromise(y):递归执行 [[Resolve]](promise, y)
      • rejectPromise(r):以 r 拒绝 promise
      • resolvePromiserejectPromise 被多次调用,仅首次有效。
    3. 若调用 then 时抛出异常 e,且 resolvePromise/rejectPromise 未被调用,则以 e 拒绝 promise
    4. then 不是函数,以 x 完成 promise
  • 示例
    // 定义 Thenable 对象
    const thenable = {
      then(resolve, reject) {
        console.log('调用 then 方法');
        setTimeout(() => {
          resolve('thenable value'); // 调用 resolvePromise
        }, 500);
      }
    };
    
    const promise = new Promise(resolve => {
      resolve(thenable); // x 是 Thenable
    });
    
    promise.then(
      value => console.log('结果:', value), // 输出:结果: thenable value
      reason => console.log('错误:', reason)
    );
    

4. x 是普通值(非对象、非函数)

  • 规则:以 x 直接完成 promise
  • 示例
    const promise = new Promise(resolve => {
      resolve(42); // x 是普通值
    });
    
    promise.then(
      value => console.log('普通值结果:', value), // 输出:普通值结果: 42
      reason => console.log('错误:', reason)
    );
    

三、解决过程在链式调用中的体现

function promise1() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('promise1 完成');
      resolve('p1 value');
    }, 1000);
  });
}

function promise2() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('promise2 完成');
      resolve('p2 value');
    }, 2000);
  });
}

// 链式调用中返回 Promise
promise1()
  .then(value => {
    console.log('then1 接收值:', value); // then1 接收值: p1 value
    return promise2(); // x 是 Promise,触发解决过程
  })
  .then(value => {
    console.log('then2 接收值:', value); // then2 接收值: p2 value(等待 promise2 完成)
  })
  .catch(error => {
    console.log('错误:', error);
  });

解决过程解析

  1. promise1 完成后,then1 返回 promise2x 是 Promise)。
  2. [[Resolve]](promise2, x) 执行:
    • promise2 处于 pending,当前 then2 的 Promise 保持 pending
    • promise2 完成时,当前 Promise 以相同值完成,触发 then2 回调。

四、解决过程中的异常处理

const promise = new Promise(resolve => {
  // 模拟 Thenable 中抛出异常
  resolve({
    then: function() {
      throw new Error('Thenable 内部错误');
    }
  });
});

promise.then(
  value => console.log('成功:', value),
  reason => console.log('错误:', reason) // 输出:错误: Thenable 内部错误
);

异常流程

  1. resolve 传入 Thenable 对象。
  2. 获取 x.then 时未出错,但调用 x.then 时抛出异常。
  3. 解决过程捕获异常,以该异常拒绝 promise

五、解决过程的核心意义

  1. 保证状态一致性:无论 x 是普通值、Promise 还是 Thenable,解决过程确保 promise 状态与 x 正确关联。
  2. 支持链式调用:通过递归处理 x,实现 Promise 链的无缝衔接。
  3. 兼容不同实现:使原生 Promise 与第三方 Promise 库(如 Bluebird)可相互操作。

理解 Promise 解决过程是掌握 Promise 异步编程的关键,尤其在处理复杂链式调用和异常场景时,能帮助开发者预测和调试代码行为。

Promise A+ 规范解读

作者 古夕
2025年6月8日 00:20

前言

任何符合 promise 规范的对象或函数都可以成为 promise,promise A plus 规范地址:promisesaplus.com/

术语

  • Promise:promise 是一个拥有 then 方法的对象或函数,其行为符合本规范。
  • 具有 then 方法(thenable):是一个定义了 then 方法的对象或函数;
  • 值(value):指任何 JavaScript 的合法值(包括 undefined,thenable 和 promise);
  • 异常(exception):是使用 throw 语句抛出的一个值。
  • 原因(reason):表示一个 promise 的拒绝原因。

promise 的状态

一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、已完成(Fulfilled)和已拒绝(Rejected)。

  • 处于等待态时,promise 需满足以下条件:可以变为「已完成」或「已拒绝」
  • 处于已完成时,promise 需满足以下条件:
    • 不能迁移至其他任何状态
    • 必须拥有一个不可变的值
  • 处于已拒绝时,promise 需满足以下条件:
    • 不能迁移至其他任何状态
    • 必须拥有一个不可变的原因

必须有一个 then 方法

一个 promise 必须提供一个 then 方法以访问其当前值和原因。 promise 的 then 方法接受两个参数:promise.then(onFulfilled, onRejected) 他们都是可选参数, 同时他们都是函数,如果 onFulfilled 或 onRejected 不是函数,则需要忽略他们。

  • 如果 onFulfilled 是一个函数
    • 当 promise 执行结束后其必须被调用,其第一个参数为 promise 的值
    • 在 promise 执行结束前其不可被调用
    • 其调用次数不可超过一次
  • 如果 onRejected 是一个函数
    • 当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的原因
    • 在 promise 被拒绝执行前其不可被调用
    • 其调用次数不可超过一次

其他规则

  • 在执行上下文堆栈仅包含平台代码之前,不得调用 onFulfilled 或 onRejected
  • onFulfilled 和 onRejected 必须被作为普通函数调用(即非实例化调用,这样函数内部 this 非严格模式下指向 window)
  • then 方法可以被同一个 promise 调用多次
    • 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
    • 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调
  • then 方法必须返回一个 promise 对象 promise2 = promise1.then(onFulfilled, onRejected);
    • 只要 onFulfilled 或者 onRejected 返回一个值 x,promise 2 都会进入 onFulfilled 状态
    • 如果 onFulfilled 或者 onRejected 抛出一个异常 e,则 promise2 必须拒绝执行,并返回拒因 e
    • 如果 onFulfilled 不是函数且 promise1 状态变为已完成,promise2 必须成功执行并返回相同的值
    • 如果 onRejected 不是函数且 promise1 状态变为已拒绝,promise2 必须执行拒绝回调并返回相同的据因
var promise1 = new Promise((resolve, reject) => {
    reject();
});
const promise2 = promise1
   .then(null, function() {
        return 123
    });

promise2
   .then(
        () => {
            console.log('promise2 已完成');
        },
        () => {
            console.log('promise2 已拒绝');
        }
    );

Promise 基础概念与实践详解

作者 古夕
2025年6月8日 00:14

在支持 ES6 的高级浏览器环境中,我们通过 new Promise() 即可构造一个 Promise 实例。这个构造函数接受一个函数作为参数,该函数分别接收两个参数 resolvereject,用于将当前实例的状态改变为 已完成已拒绝

一、Promise 实例与构造函数核心特性

  • 实例方法:Promise 实例可通过 .then 方法注册回调函数,根据实例状态(已完成/已拒绝)执行对应回调。
  • 状态控制:通过 new Promise() 构造函数的回调函数可动态更改实例状态。
  • 本质:Promise 并非新语法,而是一个新函数。
  • 执行机制:Promise 同步注册,异步执行。

二、Promise 链式调用示例

function promise1() {
    return new Promise(function(resolve, reject) {
        // 定义异步操作
        setTimeout(function() {
            console.log('1s 后输出');
            // 执行 resolve 标记为已完成,继续执行 then 链
            resolve();
        }, 1000);
    });
}

function promise2() {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log('2s 后输出');
            resolve();
        }, 2000);
    });
}

// 链式调用写法
promise1().then(function() { return promise2(); });
// 简写形式
promise1().then(promise2);

执行结果:1 秒后输出 "1s 后输出",再过 2 秒输出 "2s 后输出"。

核心逻辑:当 Promise 状态变为 已完成(调用 resolve),则执行 .then 中的下一个 Promise 函数;若状态变为 已拒绝(调用 reject),则进入异常处理函数。

三、Promise 状态处理与参数传递

function promise3() {
    return new Promise(function(resolve, reject) {
        const random = Math.random() * 10; // 生成 0-10 的随机数
        setTimeout(function() {
            if (random >= 5) {
                resolve(random); // 状态变为已完成,传递参数
            } else {
                reject(random);  // 状态变为已拒绝,传递参数
            }
        }, 1000);
    });
}

const onResolve = function(val) {
    console.log('已完成: 输出的数字是', val);
};

const onReject = function(val) {
    console.log('已拒绝: 输出的数字是', val);
};

// 方式1:通过 then 接收两个回调
promise3().then(onResolve, onReject);

// 方式2:通过 catch 捕获异常
promise3().catch(onReject).then(onResolve);

// 方式3:通过 try catch 拦截
try {
    promise3().then(onResolve);
} catch (e) {
    onReject(e);
}

关键说明

  • 三种方式可拦截 已拒绝 状态的 Promise:then 的第二个参数、.catch 方法、try catch
  • resolvereject 可传递参数,供后续 .then 回调接收。

四、Promise 核心概念总结

  1. 状态机制

    • Promise 有三种状态:进行中(pending)已完成(fulfilled)已拒绝(rejected)
    • 状态仅能从 pending 转为 fulfilledrejected,且一旦变更不可逆转。
  2. 构造函数

    • ES6 中通过 new Promise(executor) 构造实例,executor 接收 resolvereject 函数。
    • 执行 resolve 使状态变为 fulfilled,执行 reject 使状态变为 rejected
  3. 链式调用

    • 通过 .then 方法在 Promise 状态为 fulfilled 时继续执行后续逻辑。
    • resolve/reject 传递的参数可被后续 .then 回调获取。
  4. 异常处理

    • 已拒绝的 Promise 可通过 .catchthen 的第二个参数或 try catch 捕获处理。
❌
❌