回调地狱
回调地狱是一种由于过度使用嵌套回调函数而导致的代码结构不清晰、难以理解和维护的问题。一个典型例子是嵌套多个回调函数,每个回调函数都作为另一个回调函数的参数。这样会导致各个部分之间高度耦合、程序结构混乱、流程难以追踪,每个任务只能指定一个回调函数,无法使用try catch捕获到回调函数中的异常,不能直接return。
手写Promise
每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题。它也存在一些缺点,比如无法取消Promise,错误需要通过回调函数捕获。
const promise = new Promise((resolve, reject) => {resolve('success')reject('err')
})
promise.then(value => {console.log('resolve', value)
}, reason => {console.log('reject', reason)
})
// 输出 resolve success
Promise的三种状态
- Pending----Promise对象实例创建时候的初始状态
- Fulfilled----可以理解为成功的状态
- Rejected----可以理解为失败的状态
状态只能由Pending-->Fulfilled或者Pending-->Rejected,且一但发生改变就不可二次修改。
promise的链式调用
- 每次调用返回的都是一个新的Promise实例(这就是then可用链式调用的原因)
- 如果then中返回的是一个结果,会把这个结果传递下一次then中的成功回调
- 如果then中出现异常,会走下一个then的失败回调
- 在then中使用了return,那么return的值会被Promise.resolve()包装
- then中可以不传递参数,如果不传递会透到下一个then中
- catch会捕获到没有捕获的异常
细数异步场景与处理策略
- 网络请求:例如使用fetch或XMLHttpRequest进行HTTP请求。
处理策略:使用.then()和.catch()处理Promise,或者使用async/await语法。
- 定时器:如setTimeout和setInterval。
处理策略:使用回调函数或Promise封装定时器逻辑。
- 文件读写:如使用FileReader读取文件内容。
处理策略:使用回调函数或Promise封装文件读写逻辑。
- 事件监听:如点击事件、键盘事件等。
处理策略:在事件处理函数中使用异步代码,但需要注意事件循环和事件队列的处理。
- Web Workers:用于在后台线程中执行耗时的计算任务,不阻塞主线程。
处理策略:使用postMessage和onmessage进行主线程和Worker线程之间的通信。
- Promise链:多个异步操作需要依次执行。
处理策略:使用.then()串联多个Promise,或使用async/await语法。
- 并行处理:多个异步操作可以同时进行,不需要等待其他操作完成。
处理策略:使用Promise.all()等待所有Promise完成,或使用async/await结合Promise.all()。
- 错误处理:在异步操作中可能会遇到错误,需要妥善处理。
处理策略:使用.catch()捕获Promise中的错误,或使用try/catch捕获async/await中的错误。
从Promise到tj/co
tj/co是一个基于JavaScript的协程库,它允许以同步的方式编写异步代码。这个库的核心思想是将异步操作转换为可以像同步代码一样顺序执行的流程,从而简化异步编程的复杂性。
当使用co库时,可以编写一个生成器函数,并在其中使用yield关键字来暂停函数的执行,等待一个Promise的完成。当Promise完成时,生成器函数将恢复执行,并继续执行之后的代码。这个过程看起来就像是同步代码,但实际上它仍然是异步执行的。
const co = require('co');
function* fetchData() { //生成器函数,使用yield来等待两个网络请求的完成 const user = yield fetch('/users/123').then(response => response.json()); const posts = yield fetch('/posts?user=' + user.id).then(response => response.json()); return posts;
}
co(fetchData).then(posts => { console.log(posts);
}).catch(err => { console.error(err);
});
从callback到promise
最早期的异步编程主要依赖于回调函数(callback),但这种模式很快就暴露出了诸如回调地狱之类的问题,代码的可读性和维护性变得非常差。为了解决这个问题,Promise被引入作为一种更优雅的方式来处理异步操作。
async与await用法与原理详解
一个函数如果加上async,那么该函数就会返回一个Promise。
async关键字用于声明一个函数是异步的。这意味着该函数总是返回一个Promise对象。如果async函数返回一个值,这个值会被Promise对象解析为resolve值;如果async函数抛出异常,这个异常会被Promise对象解析为reject值。
await关键字只能在async函数内部使用,它会暂停async函数的执行,等待Promise的完成或拒绝,并返回Promise的结果值。正常情况下,await命令后面是一个 Promise对象,返回该对象的结果。如果不是 Promise对象,就直接返回对应的值。
- async/await只是Promise的语法糖,底层仍是基于Promise实现的,它不能用于普通的回调函数。
- async/await与Promise一样,是非阻塞的。
- async/await使得异步代码看起来像同步代码,这正是它的魔力所在。
- 使用try/catch可以很容易地捕获async函数中的错误,这是处理Promise错误的一种更简洁的方式。
- 可以在async函数中使用if语句、switch语句和for循环等,根据前一个异步操作的结果来决定是否执行下一个异步操作。
详解Promise A+规范
Promise A+规范是Promise的官方规范,它详细定义了Promise的行为和特性。这个规范的目标是确保所有的Promise实现都具有一致的行为,从而使得开发者在使用Promise时不必担心不同库或环境之间的差异。
Promise A+规范主要包括以下几个部分:
- 术语:规范首先定义了一些基本术语,如"promise"、"thenable"、"fulfilled"、"rejected"和"resolution procedure"等。
- 对象状态:一个Promise对象必须处于以下状态之一:pending(挂起)、fulfilled(实现)或rejected(拒绝)。只有异步操作的结果才能改变这个状态,任何其他操作都无法改变这个状态。
- then方法:Promise必须提供一个then方法,该方法接受两个参数:onFulfilled和onRejected,它们都是可选的,都是函数。这两个函数都将在promise被相应状态改变后异步执行。then方法返回一个新的promise,这个新的Promise的状态和值取决于onFulfilled或onRejected函数的返回值。
- Promises/A+规范中的then方法的行为:规范详细描述了then方法的执行过程,包括如何处理onFulfilled和onRejected函数的返回值,如何处理异常,以及如何处理promise链等。