0%

1. Node.js 与 JavaScript 有什么不同?

Node.js 是⼀个⽤于构建可扩展⽹络应⽤的运⾏时环境,⽽ JavaScript 是⼀种⽤于在⽹⻚上进⾏交互的脚本语⾔。主要区别包括:

  • Node.js 是基于 V8 引擎构建的服务器端平台,允许使⽤ JavaScript 编写服务器端代码,⽽ JavaScript 通常⽤于在浏览器中运⾏。

  • Node.js 提供了⼀系列内置模块和 API,使得可以直接访问⽂件系统、⽹络以及操作系统功能等;⽽在浏览器端 JavaScript 不能直接进⾏这些操作。

2. 什么场景下使⽤ Node.js?优势是什么

Node.js 适合于以下场景:

  1. ⾼并发的 I/O 密集型应⽤:如实时聊天应⽤、在线游戏、消息推送等,其中⼤量并发请求需要快速响应。

  2. 数据流处理应⽤:对于需要处理⼤量数据流的应⽤程序,例如⽇志处理、实时数据监控和分析等。

  3. 单⻚⾯应⽤程序的后端服务:作为单⻚⾯应⽤程序(SPA)的后台服务,提供 API 和数据的⽀持。

Node.js 的优势:

  • ⾮阻塞 I/O:Node.js 采⽤事件驱动、⾮阻塞 I/O 模型,能够处理⼤量并发请求⽽不会因为等待 I/O 操作⽽阻塞。

  • 使⽤ JavaScript 全栈开发:前后端都可以使⽤ JavaScript 编程语⾔,避免了在前后端之间切换语⾔带来的不必要的复杂性。

  • 丰富的包管理⼯具 npm:Node.js ⽣态系统庞⼤,拥有丰富的第三⽅库和模块可供使⽤,⽽ npm 是⼀个强⼤的包管理⼯具,让开发者轻松地使⽤和分享代码。

3. EventEmitter 做了什么?实现思路

EventEmitter 是 Node.js 核⼼模块之⼀,⽤于处理事件的订阅和发布。它允许多个监听器订阅某个特定事件,并在该事件发⽣时异步地调⽤这些监听器。

实现思路:

  1. 创建⼀个事件触发器:内部维护⼀个事件名到监听器数组的映射。当新的监听器被添加或事件被触发时,相应的操作会更新这个映射关系。

  2. 注册事件监听器:使⽤ on ⽅法注册事件监听器,在特定事件发⽣时执⾏回调函数。

  3. 触发事件:使⽤emit ⽅法触发相应的事件,对应的监听器会被异步地调⽤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class EventEmitter {
constructor() {
this.events = {};
}

on(eventName, listener) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(listener);
}

emit(eventName, ...args) {
const listeners = this.events[eventName];
if (listeners) {
for (const listener of listeners) {
listener(...args);
}
}
}
}

// 使⽤示例
const emitter = new EventEmitter();

emitter.on('greet', (name) => {
console.log(`Hello, ${name}!`);
});

emitter.emit('greet', 'John'); // 输出: Hello, John!

4. 事件循环是什么?

JavaScript 中的事件循环:在浏览器环境下,JavaScript 使⽤事件循环来处理异步操作。当有异步任务完成时,它会被放⼊⼀个事件队列中,然后按照顺序执⾏这些任务,并将结果返回给相应的回调函数。

Node.js 中的事件循环:Node.js 也具有类似的事件循环机制,但其实现⽅式略有不同。Node.js 的事件循环基于 libuv 库实现,采⽤单线程模型,通过轮询事件队列来实现异步 I/O 操作。它允许 Node.js 执⾏⾮阻塞 I/O 操作,从⽽提⾼了应⽤程序的并发能⼒和吞吐量。

  • 区别
  1. 实现机制:

    • JavaScript 在浏览器中使⽤浏览器的事件循环机制,由浏览器提供底层⽀持。
    • Node.js 使⽤ libuv 库实现事件循环,可以更好地控制系统资源和执⾏异步操作。
  2. 应⽤场景:

    • JavaScript 的事件循环主要⽤于处理浏览器中的异步任务,例如定时器、事件监听等。
    • Node.js 的事件循环适⽤于服务器端,能够处理⼤量的并发 I/O 操作,例如⽂件读写、⽹络通信等。
  3. ⾮阻塞 I/O:

    • Node.js 的事件循环允许执⾏⾮阻塞 I/O 操作,使得 Node.js 能够在等待 I/O 操作的同时继续执⾏其他任务。这是 Node.js 与浏览器 JavaScript 事件循环的重要区别之⼀。

5. 流是什么?

流(Stream)在 Node.js 中是⼀种处理⽂件、⽹络数据和其他 I/O 操作的抽象概念。它允许以⼀种⾼效的⽅式按顺序读取或写⼊数据,⽽⽆需将整个数据加载到内存中。

流可以分为可读流和可写流:

  1. 可读流:⽤于从数据源(例如⽂件、⽹络连接、标准输⼊等)读取数据。它允许逐块地读取数据,通常⽤于处理⼤量数据或连续的数据流。例如,在处理⼤型⽇志⽂件时,可读流可以逐⾏读取⽂件内容⽽不必⼀次性加载整个⽂件到内存中。

  2. 可写流:⽤于向⽬标位置(例如⽂件、⽹络连接、HTTP 响应等)写⼊数据。和可读流相反,可写流允许逐块地写⼊数据,这在需要持续产⽣输出的场景下⾮常有⽤,⽐如实时⽇志记录或 HTTP 服务器响应客户端请求。

Node.js 提供了丰富的内置模块来⽀持流的操作,包括 fs(⽂件系统)、http(HTTP 服务器与客户端)、 net(⽹络通信)等。通过使⽤流,可以有效地处理⼤量数据并提⾼性能,同时避免因为数据量过⼤⽽导致内存占⽤过⾼的问题。

6. ReadFile 和 createReadStream 函数有什么区别?

readFilecreateReadStream 是 Node.js 中⽤于读取⽂件的两种不同⽅式,它们之间有以下区别:

readFile:

  • readFile 是⼀次性将整个⽂件内容读⼊内存,并在完整读取后返回⽂件内容。适⽤于对⽂件进⾏全局操作且⽂件较⼩的情况。
  • 适合处理⼩型⽂件,但对于⼤型⽂件可能会导致内存占⽤过⾼,因为需要⼀次性加载整个⽂件。
  • 使⽤readFile 时,整个⽂件的内容将被放⼊缓冲区中,然后可以直接访问其中的数据。
1
2
3
4
5
6
const fs = require('fs');

fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});

createReadStream:

  • createReadStream 创建了⼀个可读流,它允许以块的⽅式逐步读取⽂件内容。适⽤于需要逐步处理⼤型⽂件以避免内存耗尽的情况。
  • 更适合处理⼤型⽂件,因为它避免了⼀次性读⼊整个⽂件,减少了内存占⽤。
  • 通过监听data事件来逐块读取⽂件内容,这种⽅式也更适合对数据流执⾏⼀些操作。
1
2
3
4
5
6
7
8
9
10
11
12
const fs = require('fs');
let data = '';

const readStream = fs.createReadStream('example.txt', 'utf8');

readStream.on('data', (chunk) => {
data += chunk;
});

readStream.on('end', () => {
console.log(data);
});

readFile适合处理⼩型⽂件或者需要⼀次性获取整个⽂件内容的情况;

createReadStream 适合处理⼤型⽂件,以避免内存占⽤过⾼,并⽀持逐块处理数据。

7. 如何处理 Node.js 中未捕获的异常?

  1. 全局异常处理

    可以使⽤ process 对象的 uncaughtException 事件来捕获未捕获的异常,但这种⽅式并不推荐,因为在处理完异常后,并不能保证程序状态的⼀致性。

1
2
3
4
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception: ', err); // 可以进⾏⽇志记录或其他必要的清理操作
process.exit(1); // 强制退出进程,因为状态可能不再可靠
});
  1. Domain 模块(已废弃)

在较早的 Node.js 版本中,可以使⽤ Domain 模块来捕获未捕获的异常。但由于 Domain 模块存在⼀些问题,所以在新代码中不建议使⽤。

  1. 使⽤ try…catch

在合适的地⽅使⽤ try…catch 捕获可能出现异常的代码块。这种⽅式适⽤于同步代码和部分异步代码。

1
2
3
4
5
try {
// 可能会抛出异常的代码
} catch (err) {
// 处理异常
}
  1. Promise 的 catch ⽅法

对于使⽤ Promise 的异步操作,可以使⽤catch ⽅法来捕获异常。

1
2
3
4
5
6
7
somePromiseFunction()
.then((result) => {
// 处理成功情况
})
.catch((err) => {
// 处理异常
});
  1. Async/Await 的 try…catch

对于使⽤ async/await 的异步操作,可以使⽤ try…catch 来捕获异常。

1
2
3
4
5
6
7
8
async function someAsyncFunction() {
try {
const result = await somePromiseFunction();
// 处理成功情况
} catch (err) {
// 处理异常
}
}

8. Node.Js 能否充分利⽤多核处理器?

Node.js 是单线程的,因此在默认情况下⽆法充分利⽤多核处理器。但可以通过创建⼦进程(cluster 模块或 child_process 模块),每个⼦进程都可以运⾏在不同的 CPU 核⼼上,从⽽实现多核利⽤。使⽤ cluster 模块可以简化多核处理器的利⽤,它允许将应⽤程序复制到多个⼦进程中,并通过负载均衡来充分利⽤多核处理器。

9. 反应堆设计模式是什么?

反应堆设计模式(Reactor Design Pattern)是⼀种⽤于处理并发请求和事件驱动的编程模式。该模式主要包括以下⼏个关键组件:

  1. 事件驱动:基于事件的系统,其核⼼是⼀个事件循环(event loop),负责监听和分发事件。

  2. I/O 多路复⽤:使⽤诸如 select、poll 或 epoll 等机制,实现同时监视多个⽂件描述符的状态变化,从⽽通过⾮阻塞⽅式处理 I/O 事件。

  3. 回调函数:当特定事件发⽣时,执⾏预定义的回调函数来处理这些事件。这使得程序可以异步地响应各种类型的事件。

  4. ⾮阻塞 I/O:以⾮阻塞的⽅式进⾏ I/O 操作,允许系统在等待数据准备好时继续执⾏其他任务,⽽不必等待整个操作完成。

在 Node.js 中,事件驱动和⾮阻塞 I/O 是通过反应堆设计模式来实现的。这种模式使得 Node.js 能够⾼效地处理⼤量并发请求,并且在等待 I/O 操作完成时释放资源,不会阻塞其他请求的处理。

10. 单线程与多线程⽹络后端相⽐有哪些好处?

单线程⽹络后端的好处:

  1. 较少的资源消耗:单线程⽹络后端通常⽐多线程⽹络后端消耗的系统资源更少,因为不需要额外的线程管理开销。

  2. 避免竞争条件:单线程模型避免了在多线程中可能出现的竞争条件,使得代码更加简单和可预测。

  3. 易于调试:由于单线程中没有并发问题,因此调试起来相对简单。

多线程⽹络后端的好处:

  1. 更好的利⽤多核处理器:多线程⽹络后端可以更充分地利⽤多核处理器,提⾼系统的并发处理能⼒。

  2. 避免阻塞:即使某个线程被阻塞,其他线程仍然可以继续执⾏,从⽽提⾼系统的响应速度。

单线程适合于 I/O 密集型任务,因为它能够以⾮阻塞的⽅式处理⼤量的并发请求,⽽多线程适合于 CPU 密集型任务,因为它能够更充分地利⽤多核处理器的优势。选择单线程或多线程⽹络后端取决于具体的应⽤场景和需求。

11. REPL 是什么?

REPL 是 “Read-Eval-Print Loop” 的缩写,是指⼀种交互式编程环境或解释器环境。在这种环境中,⽤户可以输⼊⼀⾏代码,系统会⽴即对其进⾏解析、求值并输出结果,然后等待⽤户下⼀⾏输⼊。

在 Node.js 中,REPL 是⼀个内置的⼯具,允许开发者在命令⾏中直接输⼊ JavaScript 代码,并且能够⽴即得到执⾏结果。这使得开发者能够快速尝试、测试和调试 JavaScript 代码,⽽⽆需编写完整的程序或脚本⽂件。通过 REPL,开发者可以实时地交互式地探索 JavaScript 语⾔的特性、API 和功能,同时也⽅便⽤于代码⽚段 的测试和验证。这在学习、原型设计以及快速问题排查上都⾮常有⽤。

12. process.nextTick 和 setImmediate 有什么区别?

process.nextTick setImmediate 是 Node.js 中⽤于调度异步操作的两个重要机制,它们之间具有以下区别:

process.nextTick:

  • process.nextTick 的回调函数会在当前操作结束后⽴即执⾏,⽽且会优先于微任务队列中的 Promise 回调。
  • 这意味着 process.nextTick 的回调函数会在同⼀个事件循环中的 I/O 事件和定时器之前执 ⾏。

setImmediate:

  • setImmediate 的回调函数会在当前事件循环迭代的末尾执⾏。它会在 I/O 事件和定时器之后但在下⼀个事件循环之前执⾏。
  • 如果在当前事件循环没有其他待执⾏的操作,则 setImmediate 的回调函数会⽴即执⾏。

process.nextTick 的回调函数会在当前操作结束后⽴即执⾏,⽽ setImmediate 的回调函数 会在当前事件循环迭代的末尾执⾏,可以理解为在执⾏顺序上的微⼩差异。

13. stub 是什么?

在软件测试中,”stub” 是指⼀种测试替身或者模拟对象,⽤于代替真实的组件以便进⾏测试。⼀般情况下,stub ⽤于模拟外部依赖,例如数据库、⽹络请求、⽂件系统等,使得测试可以专注于被测代码本身,⽽不受外部环境的影响。

Stubs 主要⽤于以下⼏个⽅⾯:

  1. 隔离测试:通过使⽤ stub 替代外部依赖,能够隔离被测试的代码,确保测试的独⽴性。

  2. 模拟⾏为:可以通过 stub 模拟外部组件的⾏为,从⽽测试特定的场景和条件。

  3. 简化测试:使得测试更加可控和可预测,同时避免了测试所需的外部资源,⽐如数据库连接或者⽹络请求。

通过 stubs 可以让开发者更轻松地编写单元测试,并且确保测试环境的独⽴性,使得针对特定功能的验证变得更加容易。

14. 为什么在 express 中分离“应⽤程序”和“服务器”是⼀种好的做法?

  1. 模块化:通过将应⽤程序与服务器分离,可以使代码更加模块化和可重⽤。这样做有助于提⾼代码的清晰度和可维护性,并且使得不同部分的代码更容易被复⽤于其他项⽬或场景中。

  2. 便于测试:分离应⽤程序和服务器使得单元测试更加容易。可以针对应⽤程序和服务器各⾃进⾏测试,⽽不必同时测试两者的组合。这样的分离有助于改善代码质量和开发效率。

  3. 灵活性:将应⽤程序和服务器分离可以使得应⽤程序更容易迁移到其他⽀持 HTTP 协议的服务器上。这样做使得切换服务器框架变得更加容易,增加了开发⼈员在后期选择、更换服务器时的灵活性。

  4. 清晰的职责分离:将应⽤程序与服务器分离能够更清晰地区分它们各⾃的职责。应⽤程序专注于业务逻辑和路由处理,⽽服务器则负责处理底层的⽹络通信和请求转发,使得代码更容易理解和维护。

15. 什么是 yarn 和 npm?为什么要⽤ yarn 代替 npm 呢?

Yarn 和 npm:

  • npm(Node Package Manager):是 Node.js 的包管理⼯具,⽤于安装、更新和管理 JavaScript 包及其依赖。npm 是 Node.js 默认的包管理器,提供了⼀个庞⼤的包仓库,供开发者共享和下载代码。

  • Yarn:也是 JavaScript 的包管理⼯具,旨在解决 npm 的⼀些限制,并提供更快、更可靠的依赖管理。Yarn 由 Facebook、Google、Exponent 和 Tilde 共同开发,它通过并⾏下载、缓存和锁定依赖版本等功能提⾼了性能和安全性。

为什么要⽤ Yarn 代替 npm 呢?

  1. 性能:Yarn 在安装包时通常⽐ npm 更快,因为它可以并⾏下载依赖项,⽽ npm 默认是串⾏下载。

  2. 缓存:Yarn 会缓存每个下载过的软件包,使得再次安装时速度更快。

  3. 版本锁定:Yarn 使⽤ yarn.lock ⽂件来确保每个开发者和持续集成环境都使⽤相同的依赖版本,⽽ npm 则需要⼿动创建 package-lock.json ⽂件。

  4. 定性:Yarn 的更新频率较低,这意味着它更加稳定,且遵循语义化版本控制规则。

  5. 安全性:Yarn 有⼀个可选的安全检查⼯具yarn audit,⽤于检查已安装的软件包中是否存在安全漏洞。

16. 如何优化 Node.js 应⽤程序的性能?

  1. 使⽤适当的数据结构和算法:选择合适的数据结构和算法对于提⾼性能⾄关重要。⽐如,针对特定需求选择合适的集合类型、搜索算法或排序算法。

  2. 异步编程:利⽤ Node.js 的⾮阻塞 I/O 特性,采⽤异步编程⽅式,避免阻塞操作以提⾼并发处理能⼒。

  3. 利⽤事件驱动:充分利⽤ Node.js 的事件驱动机制,通过订阅和响应事件来进⾏⾮阻塞的处理。这样可以提⾼系统的响应速度。

  4. 合理使⽤缓存:利⽤内存缓存结果,避免重复计算。可以使⽤内置的缓存模块或者第三⽅库(如 Redis)来加速数据访问。

  5. 压缩和合并⽂件:对静态资源⽂件(如 CSS、JavaScript)进⾏压缩和合并,减少⽹络传输时间和请求次数。

  6. 数据库优化:通过索引、合理的查询语句和连接池等⼿段对数据库进⾏优化,提⾼数据库读写性能。

  7. 调优事件循环:了解事件循环的机制,避免⻓时间运⾏的同步操作,尽量将耗时的任务交给 Worker Threads 或者外部服务来处理。

  8. 监控和调优:使⽤⼯具进⾏性能监控和分析,例如 Node.js 的内置性能⼯具、第三⽅的 APM ⼯具(Application Performance Monitoring),从⽽找出性能瓶颈并进⾏相应的调优。

  9. 横向扩展:根据需求和负载情况,考虑采⽤横向扩展的⽅式增加服务器节点,实现负载均衡。

17. 什么是中间件(Middleware)?在 Express 框架中如何使⽤中间件?

中间件(Middleware) 是指在应⽤程序处理请求和发送响应之间执⾏的⼀系列函数。在 Express 框架中,中间件允许开发者可以对 HTTP 请求进⾏拦截、处理或者修改,并且在请求流转到路由处理前或者之后执⾏额外的逻辑。

使⽤中间件有助于实现以下功能:

  1. 执⾏路由特定任务:例如身份验证、⽇志记录、数据解析或安全检查。

  2. 修改请求和响应对象:可以在中间件中修改请求和响应对象,添加⾃定义的属性或⽅法,以便后续的处理能够使⽤这些信息。

  3. 错误处理:捕获并处理请求处理过程中发⽣的错误,确保应⽤程序的稳定性。

在 Express 框架中如何使⽤中间件:

  1. 内置中间件:Express 提供了若⼲内置的中间件,可以通过 app.use ⽅法来使⽤,⽐如 express.json() ⽤于解析 JSON 数据、 express.urlencoded() ⽤于解析表单数据等。

  2. 第三⽅中间件:除了内置中间件外,还可以使⽤第三⽅中间件,如 helmet ⽤于增强应⽤程序的安全性、 morgan ⽤于记录⽇志等,同样是通过 app.use ⽅法来使⽤。

  3. ⾃定义中间件:开发者可以编写⾃⼰的中间件函数,并且通过 app.use ⽅法来使⽤这些⾃定义中间件。中间件函数需要接受 reqresnext 三个参数,分别表示请求对象、响应对象和下⼀个中间件函数。

1
2
3
4
5
6
7
8
9
10
11
12
// 内置中间件
app.use(express.json());

// 第三⽅中间件
const helmet = require('helmet');
app.use(helmet());

// ⾃定义中间件
app.use(function (req, res, next) {
// 执⾏⼀些额外的逻辑
next();
});

中间件可以单独使⽤,也可以按顺序组合使⽤,通过 app.use ⽅法将它们添加到 Express 应⽤程序的请求处理链中,从⽽影响请求的处理流程。

18. Node.js 中的模块加载机制是什么?

在 Node.js 中,模块加载机制遵循 CommonJS 模块规范。这种模块加载机制的核⼼思想是通过使⽤ require 函数引⼊模块,并且每个⽂件都被视为⼀个独⽴的模块。

Node.js 的模块加载机制如下:

  1. 模块导⼊(require):在 Node.js 中,使⽤ require 函数来导⼊其他模块。当执⾏ require 时,Node.js 会⾃动查找、加载并执⾏指定的模块。

  2. 模块导出:要使⼀个模块可以被其他模块使⽤,需要使⽤ module.exportsexports 导出模块内容。

  3. 模块路径解析:Node.js 使⽤特定的路径解析算法来确定需要加载的模块。如果模块名不是⼀个核⼼模块或者相对/绝对路径,Node.js 会根据模块路径从当前⽬录向上逐级查找 node_modules⽂件夹中的模块。

  4. 缓存:Node.js 对已经加载过的模块会进⾏缓存,以免重复加载,提⾼加载效率。

1
2
3
4
5
6
7
// 导⼊模块
const myModule = require('./myModule');

// 导出模块
module.exports = {
// 模块内容
};

Node.js 的模块加载机制遵循 CommonJS 规范,通过 requiremodule.exports 实现模块的导⼊和导出,同时利⽤路径解析和缓存机制提⾼模块加载的性能。

1. 异步回调

1.1 回调地狱

在需要多个操作的时候,会导致多个回调函数嵌套,导致代码不够直观,就是常说的回调地狱

1.2 并行结果

如果几个异步操作之间并没有前后顺序之分,但需要等多个异步操作都完成后才能执行后续的任务,无法实现并行节约时间

2. Promise

Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果。 什么时候会用到过一段时间?答案是异步操作,异步是指可能比较长时间才有结果的操作,例如网络请求、读取本地文件等

3. Promise的三种状态

  • Pending Promise对象实例创建时候的初始状态
  • Fulfilled 可以理解为成功的状态
  • Rejected 可以理解为失败的状态

then 方法就是用来指定Promise 对象的状态改变时确定执行的操作,resolve 时执行第一个函数(onFulfilled),reject 时执行第二个函数(onRejected)

4. 构造一个Promise

4.1 使用Promise

1
2
3
4
5
6
7
8
9
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
if(Math.random()>0.5)
resolve('This is resolve!');
else
reject('This is reject!');
}, 1000);
});
promise.then(Fulfilled,Rejected)
  • 构造一个Promise实例需要给Promise构造函数传入一个函数。
  • 传入的函数需要有两个形参,两个形参都是function类型的参数。
    • 第一个形参运行后会让Promise实例处于resolve状态,所以我们一般给第一个形参命名为resolve,使 Promise 对象的状态改变成成功,同时传递一个参数用于后续成功后的操作
    • 第二个形参运行后会让Promise实例处于reject状态,所以我们一般给第二个形参命名为reject,将 Promise 对象的状态改变为失败,同时将错误的信息传递到后续错误处理的操作

4.2 es5模拟Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Promise(fn) {
fn((data)=> {
this.success(data);
}, (error)=> {
this.error();
});
}

Promise.prototype.resolve = function (data) {
this.success(data);
}

Promise.prototype.reject = function (error) {
this.error(error);
}

Promise.prototype.then = function (success, error) {
this.success = success;
this.error = error;
}

4.3 es6模拟Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Promise {
constructor(fn) {
fn((data)=> {
this.success(data);
}, (error)=> {
this.error();
});
}

resolve(data) {
this.success(data);
}

reject(error) {
this.error(error);
}

then(success, error) {
this.success = success;
this.error = error;
console.log(this);
}
}

5. promise 做为函数的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function ajaxPromise (queryUrl) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', queryUrl, true);
xhr.send(null);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(xhr.responseText);
}
}
}
});
}

ajaxPromise('http://www.baidu.com')
.then((value) => {
console.log(value);
})
.catch((err) => {
console.error(err);
});

6.promise的链式调用

  • 每次调用返回的都是一个新的Promise实例
  • 链式调用的参数通过返回值传递

then可以使用链式调用的写法原因在于,每一次执行该方法时总是会返回一个Promise对象

1
2
3
4
5
6
7
8
9
10
11
readFile('1.txt').then(function (data) {
console.log(data);
return data;
}).then(function (data) {
console.log(data);
return readFile(data);
}).then(function (data) {
console.log(data);
}).catch(function(err){
console.log(err);
});

7.promise API

7.1 Promise.all

  • 参数:接受一个数组,数组内都是Promise实例
  • 返回值:返回一个Promise实例,这个Promise实例的状态转移取决于参数的Promise实例的状态变化。当参数中所有的实例都处于resolve状态时,返回的Promise实例会变为resolve状态。如果参数中任意一个实例处于reject状态,返回的Promise实例变为reject状态。
    1
    2
    3
    Promise.all([p1, p2]).then(function (result) {
    console.log(result); // [ '2.txt', '2' ]
    });

不管两个promise谁先完成,Promise.all 方法会按照数组里面的顺序将结果返回

7.2 Promise.race

  • 参数:接受一个数组,数组内都是Promise实例
  • 返回值:返回一个Promise实例,这个Promise实例的状态转移取决于参数的Promise实例的状态变化。当参数中任何一个实例处于resolve状态时,返回的Promise实例会变为resolve状态。如果参数中任意一个实例处于reject状态,返回的Promise实例变为reject状态。
    1
    2
    3
    Promise.race([p1, p2]).then(function (result) {
    console.log(result); // [ '2.txt', '2' ]
    });

7.3 Promise.resolve

  • 返回一个Promise实例,这个实例处于resolve状态。

根据传入的参数不同有不同的功能:

  • 值(对象、数组、字符串等):作为resolve传递出去的值
  • Promise实例:原封不动返回

7.4 Promise.reject

返回一个Promise实例,这个实例处于reject状态。

  • 参数一般就是抛出的错误信息。

8. q

Q是一个在Javascript中实现promise的模块

8.1 q的基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Q = require('q');
var fs = require('fs');
function read(filename) {
var deferred = Q.defer();
fs.readFile(filename,'utf8', function (err, data) {
if(err){
deferred.reject(err);
}else{
deferred.resolve(data);
}
});
return deferred.promise;
}

read('1.txt1').then(function(data){
console.log(data);
},function(error){
console.error(error);
});

8.2 q的简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
defer(){
var _success,_error;
return {
resolve(data){
_success(data);
},
reject(err){
_error(err);
},
promise:{
then(success,error){
_success = success;
_error = error;
}
}
}
}
}

8.3 q的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var defer = function () {
var pending = [], value;
return {
resolve: function (_value) {
if (pending) {
value = _value;
for (var i = 0, ii = pending.length; i < ii; i++) {
var callback = pending[i];
callback(value);
}
pending = undefined;
}
},
promise: {
then: function (callback) {
if (pending) {
pending.push(callback);
} else {
callback(value);
}
}
}
};
};

9. bluebird

实现 promise 标准的库是功能最全,速度最快的一个库

9.1 bluebird经典使用

1
2
3
4
5
6
7
8
9
10
11
12
var Promise = require('./bluebird');

var readFile = Promise.promisify(require("fs").readFile);
readFile("1.txt", "utf8").then(function(contents) {
console.log(contents);
})

var fs = Promise.promisifyAll(require("fs"));

fs.readFileAsync("1.txt", "utf8").then(function (contents) {
console.log(contents);
})

9.2 bluebird简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module.exports = {
promisify(fn){
return function () {
var args = Array.from(arguments);
return new Promise(function (resolve, reject) {
fn.apply(null, args.concat(function (err) {
if (err) {
reject(err);
} else {
resolve(arguments[1])
}
}));
})
}
},
promisifyAll(obj){
for(var attr in obj){
if(obj.hasOwnProperty(attr) && typeof obj[attr] =='function'){
obj[attr+'Async'] = this.promisify(obj[attr]);
}
}
return obj;
}
}

10. 动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>move</title>
<style>
.square{
width:40px;
height:40px;
border-radius: 50%;
}
.square1{
background-color: red;
}
.square2{
background-color: yellow;
}
.square3{
background-color: blue;
}
</style>
</head>
<body>
<div class="square square1" style="margin-left: 0"></div>
<div class="square square2" style="margin-left: 0"></div>
<div class="square square3" style="margin-left: 0"></div>
</body>
<script>
var square1 = document.querySelector('.square1');
var square2 = document.querySelector('.square2');
var square3 = document.querySelector('.square3');

/*function move(element,target,resolve){
let timer = setInterval(function(){
var marginLeft = parseInt(element.style.marginLeft, 10);
if(marginLeft == target){
resolve();
}else{
element.style.marginLeft = ++marginLeft+'px';
}
},13);
}*/
function move(element,target,resolve){
let current = 0;
let timer = setInterval(function(){
element.style.transform=`translateX(${++current}px)`;
if(current>target){
clearInterval(timer);
resolve();
};
},13);
}
function animate(element,target){
return new Promise(function(resolve,reject){
move(element,target,resolve);
});
}
animate(square1,100)
.then(function(){
return animate(square2,100);
})
.then(function(){
return animate(square3,100);
});
</script>
</html>

11. co

11.1 co初体验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
let fs = require('fs');
function getNumber(){
return new Promise(function (resolve,reject) {
setTimeout(function(){
let number = Math.random();
if(number >.5){
resolve(number);
}else{
reject('数字太小');
}
},1000);
});
}
function *read(){
let a = yield getNumber();
console.log(a);
let b = yield 'b';
console.log(b);
let c = yield getNumber();
console.log(c);
}

function co(gen){
return new Promise(function(resolve,reject){
let g = gen();
function next(lastValue){
let {done,value} = g.next(lastValue);
if(done){
resolve(lastValue);
}else{
if(value instanceof Promise){
value.then(next,function(val){
reject(val);
});
}else{
next(value);
}
}
}
next();
});
}
co(read).then(function(data){
console.log(data);
},function(reason){
console.log(reason);
});

11.2 co连续读文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let fs = require('fs');
function readFile(filename){
return new Promise(function (resolve,reject) {
fs.readFile(filename,'utf8',function(err,data){
if(err)
reject(err);
else
resolve(data);
})
});
}
function *read(){
let a = yield readFile('./1.txt');
console.log(a);
let b = yield readFile('./2.txt');
console.log(b);
}

function co(gen){
let g = gen();
function next(val){
let {done,value} = g.next(val);
if(!done){
value.then(next);
}
}
next();
}

12. Promise/A+完整实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
function Promise(executor) {
let self = this;
self.status = "pending";
self.value = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(function () { // 异步执行所有的回调函数
if (self.status == 'pending') {
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(item => item(value));
}
});

}

function reject(value) {
setTimeout(function () {
if (self.status == 'pending') {
self.value = value;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(item => item(value));
}
});
}

try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('循环引用'));
}
let then, called;

if (x != null && ((typeof x == 'object' || typeof x == 'function'))) {
try {
then = x.then;
if (typeof then == 'function') {
then.call(x, function (y) {
if (called)return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, function (r) {
if (called)return;
called = true;
reject(r);
});
} else {
resolve(x);
}
} catch (e) {
if (called)return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : function (value) {
return value
};
onRejected = typeof onRejected == 'function' ? onRejected : function (value) {
throw value
};
let promise2;
if (self.status == 'resolved') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});

});
}
if (self.status == 'rejected') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRejected(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
if (self.status == 'pending') {
promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallbacks.push(function (value) {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push(function (value) {
try {
let x = onRejected(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
return promise2;
}
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
}
Promise.all = function (promises) {
return new Promise(function (resolve, reject) {
let result = [];
let count = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(function (data) {
result[i] = data;
if (++count == promises.length) {
resolve(result);
}
}, function (err) {
reject(err);
});
}
});
}

Promise.deferred = Promise.defer = function () {
var defer = {};
defer.promise = new Promise(function (resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
})
return defer;
}
/**
* npm i -g promises-aplus-tests
* promises-aplus-tests Promise.js
*/
try {
module.exports = Promise
} catch (e) {
}

13. 资源