前端面试指南
基础知识
基础
语法
框架
工程
网络
性能
插件
操作系统
后端
业务思考
工程化
通用性
应用框架
业务
低代码
笔试实践
数据结构
模板解析
设计模式
正则表达式
本文档使用 MrDoc 发布
-
+
首页
语法
## 如何实现一个上中下三行布局,顶部和底部最小高度是 100px,中间自适应? ## 如何判断一个元素 CSS 样式溢出,从而可以选择性的加 title 或者 Tooltip? ## 如何让 CSS 元素左侧自动溢出(... 溢出在左侧)? The **`direction`** CSS property sets the direction of text, table columns, and horizontal overflow. Use `rtl` for languages written from right to left (like Hebrew or Arabic), and `ltr` for those written from left to right (like English and most other languages). 具体查看:[developer.mozilla.org/en-US/docs/…](https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FCSS%2Fdirection "https://developer.mozilla.org/en-US/docs/Web/CSS/direction") ## 什么是沙箱?浏览器的沙箱有什么作用? ## 如何处理浏览器中表单项的密码自动填充问题? ## Hash 和 History 路由的区别和优缺点? ## JavaScript 中对象的属性描述符有哪些?分别有什么作用? ## JavaScript 中 console 有哪些 api ? The **`console`** object provides access to the browser's debugging console (e.g. the [Web console](https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FTools%2FWeb_Console "https://developer.mozilla.org/en-US/docs/Tools/Web_Console") in Firefox). The specifics of how it works varies from browser to browser, but there is a *de facto* set of features that are typically provided. 这里列出一些我常用的 API: * console.log * console.error * console.time * console.timeEnd * console.group 具体查看:[developer.mozilla.org/en-US/docs/…](https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2Fconsole "https://developer.mozilla.org/en-US/docs/Web/API/console") ## 简单对比一下 Callback、Promise、Generator、Async 几个异步 API 的优劣? 在 JavaScript 中利用[事件循环机制](https://juejin.cn/post/6844903843197616136#heading-3 "https://juejin.cn/post/6844903843197616136#heading-3")(Event Loop)可以在单线程中实现非阻塞式、异步的操作。例如 * Node.js 中的 Callback、[EventEmitter](https://link.juejin.cn/?target=http%3A%2F%2Fnodejs.cn%2Fapi%2Fevents.html%23events_class_eventemitter "http://nodejs.cn/api/events.html#events_class_eventemitter")、[Stream](https://link.juejin.cn/?target=http%3A%2F%2Fnodejs.cn%2Fapi%2Fstream.html "http://nodejs.cn/api/stream.html") * ES6 中的 [Promise](https://link.juejin.cn/?target=https%3A%2F%2Fes6.ruanyifeng.com%2F%23docs%2Fpromise "https://es6.ruanyifeng.com/#docs/promise")、[Generator](https://link.juejin.cn/?target=https%3A%2F%2Fes6.ruanyifeng.com%2F%23docs%2Fgenerator-async "https://es6.ruanyifeng.com/#docs/generator-async") * ES2017 中的 [Async](https://link.juejin.cn/?target=https%3A%2F%2Fes6.ruanyifeng.com%2F%23docs%2Fasync "https://es6.ruanyifeng.com/#docs/async") * 三方库 RxJS、[Q](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fkriskowal%2Fq "https://github.com/kriskowal/q") 、[Co、](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Ftj%2Fco "https://github.com/tj/co")[Bluebird](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fpetkaantonov%2Fbluebird "https://github.com/petkaantonov/bluebird") 我们重点来看一下常用的几种编程方式(Callback、Promise、Generator、Async)在语法糖上带来的优劣对比。 **Callback** Callback(回调函数)是在 Web 前端开发中经常会使用的编程方式。这里举一个常用的定时器示例: ```typescript export interface IObj { value: string; deferExec(): void; deferExecAnonymous(): void; console(): void; } export const obj: IObj = { value: 'hello', deferExecBind() { // 使用箭头函数可达到一样的效果 setTimeout(this.console.bind(this), 1000); }, deferExec() { setTimeout(this.console, 1000); }, console() { console.log(this.value); }, }; obj.deferExecBind(); // hello obj.deferExec(); // undefined ``` 回调函数经常会因为调用环境的变化而导致 `this` 的指向性变化。除此之外,使用回调函数来处理多个继发的异步任务时容易导致回调地狱(Callback Hell): ```typescript fs.readFile(fileA, 'utf-8', function (err, data) { fs.readFile(fileB, 'utf-8', function (err, data) { fs.readFile(fileC, 'utf-8', function (err, data) { fs.readFile(fileD, 'utf-8', function (err, data) { // 假设在业务中 fileD 的读写依次依赖 fileA、fileB 和 fileC // 或者经常也可以在业务中看到多个 HTTP 请求的操作有前后依赖(继发 HTTP 请求) // 这些异步任务之间纵向嵌套强耦合,无法进行横向复用 // 如果某个异步发生变化,那它的所有上层或下层回调可能都需要跟着变化(比如 fileA 和 fileB 的依赖关系倒置) // 因此称这种现象为 回调地狱 // .... }); }); }); }); ``` 回调函数不能通过 `return` 返回数据,比如我们希望调用带有回调参数的函数并返回异步执行的结果时,只能通过再次回调的方式进行参数传递: ```typescript // 希望延迟 3s 后执行并拿到结果 function getAsyncResult(result: number) { setTimeout(() => { return result * 3; }, 1000); } // 尽管这是常规的编程思维方式 const result = getAsyncResult(3000); // 但是打印 undefined console.log('result: ', result); function getAsyncResultWithCb(result: number, cb: (result: number) => void) { setTimeout(() => { cb(result * 3); }, 1000); } // 通过回调的形式获取结果 getAsyncResultWithCb(3000, (result) => { console.log('result: ', result); // 9000 }); ``` 对于 JavaScript 中标准的异步 API 可能无法通过在外部进行 `try...catch...` 的方式进行错误捕获: ```typescript try { setTimeout(() => { // 下述是异常代码 // 你可以在回调函数的内部进行 try...catch... console.log(a.b.c) }, 1000) } catch(err) { // 这里不会执行 // 进程会被终止 console.error(err) } ``` 上述示例讲述的都是 JavaScript 中标准的异步 API ,如果使用一些三方的异步 API 并且提供了回调能力时,这些 API 可能是非受信的,在真正使用的时候会因为 **执行反转** (回调函数的执行权在三方库中)导致以下一些问题: * 使用者的回调函数设计没有进行错误捕获,而恰恰三方库进行了错误捕获却没有抛出错误处理信息,此时使用者很难感知到自己设计的回调函数是否有错误 * 使用者难以感知到三方库的回调时机和回调次数,这个回调函数执行的权利控制在三方库手中 * 使用者无法更改三方库提供的回调参数,回调参数可能无法满足使用者的诉求 * ... 举个简单的例子: ```typescript interface ILib<T> { params: T; emit(params: T): void; on(callback: (params: T) => void): void; } // 假设以下是一个三方库,并发布成了 npm 包 export const lib: ILib<string> = { params: '', emit(params) { this.params = params; }, on(callback) { try { // callback 回调执行权在 lib 上 // lib 库可以决定回调执行多次 callback(this.params); callback(this.params); callback(this.params); // lib 库甚至可以决定回调延迟执行 // 异步执行回调函数 setTimeout(() => { callback(this.params); }, 3000); } catch (err) { // 假设 lib 库的捕获没有抛出任何异常信息 } }, }; // 开发者引入 lib 库开始使用 lib.emit('hello'); lib.on((value) => { // 使用者希望 on 里的回调只执行一次 // 这里的回调函数的执行时机是由三方库 lib 决定 // 实际上打印四次,并且其中一次是异步执行 console.log(value); }); lib.on((value) => { // 下述是异常代码 // 但是执行下述代码不会抛出任何异常信息 // 开发者无法感知自己的代码设计错误 console.log(value.a.b.c) }); ``` **Promise** Callback 的异步操作形式除了会造成回调地狱,还会造成难以测试的问题。ES6 中的 Promise (基于 [ Promise A +](https://link.juejin.cn/?target=https%3A%2F%2Fpromisesaplus.com%2F "https://promisesaplus.com/") 规范的异步编程解决方案)利用[有限状态机](https://link.juejin.cn/?target=http%3A%2F%2Fwww.ruanyifeng.com%2Fblog%2F2013%2F09%2Ffinite-state_machine_for_javascript.html "http://www.ruanyifeng.com/blog/2013/09/finite-state_machine_for_javascript.html")的原理来解决异步的处理问题,Promise 对象提供了统一的异步编程 API,它的特点如下: * Promise 对象的执行状态不受外界影响。Promise 对象的异步操作有三种状态: `pending`(进行中)、 `fulfilled`(已成功)和 `rejected`(已失败) ,只有 Promise 对象本身的异步操作结果可以决定当前的执行状态,任何其他的操作无法改变状态的结果 * Promise 对象的执行状态不可变。Promise 的状态只有两种变化可能:从 `pending`(进行中)变为 `fulfilled`(已成功)或从 `pending`(进行中)变为 `rejected`(已失败) > 温馨提示:有限状态机提供了一种优雅的解决方式,异步的处理本身可以通过异步状态的变化来触发相应的操作,这会比回调函数在逻辑上的处理更加合理,也可以降低代码的复杂度。 Promise 对象的执行状态不可变示例如下: ```typescript const promise = new Promise<number>((resolve, reject) => { // 状态变更为 fulfilled 并返回结果 1 后不会再变更状态 resolve(1); // 不会变更状态 reject(4); }); promise .then((result) => { // 在 ES 6 中 Promise 的 then 回调执行是异步执行(微任务) // 在当前 then 被调用的那轮事件循环(Event Loop)的末尾执行 console.log('result: ', result); }) .catch((error) => { // 不执行 console.error('error: ', error); }); ``` 假设要实现两个继发的 HTTP 请求,第一个请求接口返回的数据是第二个请求接口的参数,使用回调函数的实现方式如下所示(这里使用 `setTimeout` 来指代异步请求): ```typescript // 回调地狱 const doubble = (result: number, callback: (finallResult: number) => void) => { // Mock 第一个异步请求 setTimeout(() => { // Mock 第二个异步请求(假设第二个请求的参数依赖第一个请求的返回结果) setTimeout(() => { callback(result * 2); }, 2000); }, 1000); }; doubble(1000, (result) => { console.log('result: ', result); }); ``` > 温馨提示:继发请求的依赖关系非常常见,例如人员基本信息管理系统的开发中,经常需要先展示组织树结构,并默认加载第一个组织下的人员列表信息。 如果采用 Promise 的处理方式则可以规避上述常见的回调地狱问题: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { // Mock 异步请求 // 将 resolve 改成 reject 会被 catch 捕获 setTimeout(() => resolve(result), 1000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { // Mock 异步请求 // 将 resolve 改成 reject 会被 catch 捕获 setTimeout(() => resolve(result * 2), 1000); }); }; firstPromise(1000) .then((result) => { return nextPromise(result); }) .then((result) => { // 2s 后打印 2000 console.log('result: ', result); }) // 任何一个 Promise 到达 rejected 状态都能被 catch 捕获 .catch((err) => { console.error('err: ', err); }); ``` Promise 的错误回调可以同时捕获 `firstPromise` 和 `nextPromise` 两个函数的 `rejected` 状态。接下来考虑以下调用场景: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { // Mock 异步请求 setTimeout(() => resolve(result), 1000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { // Mock 异步请求 setTimeout(() => resolve(result * 2), 1000); }); }; firstPromise(1000) .then((result) => { nextPromise(result).then((result) => { // 后打印 console.log('nextPromise result: ', result); }); }) .then((result) => { // 先打印 // 由于上一个 then 没有返回值,这里打印 undefined console.log('firstPromise result: ', result); }) .catch((err) => { console.error('err: ', err); }); ``` 首先 Promise 可以注册多个 `then`(放在一个执行队列里),并且这些 `then` 会根据上一次返回值的结果依次执行。除此之外,各个 Promise 的 `then` 执行互不干扰。 我们将示例进行简单的变换: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { // Mock 异步请求 setTimeout(() => resolve(result), 1000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { // Mock 异步请求 setTimeout(() => resolve(result * 2), 1000); }); }; firstPromise(1000) .then((result) => { // 返回了 nextPromise 的 then 执行后的结果 return nextPromise(result).then((result) => { return result; }); }) // 接着 nextPromise 的 then 执行的返回结果继续执行 .then((result) => { // 2s 后打印 2000 console.log('nextPromise result: ', result); }) .catch((err) => { console.error('err: ', err); }); ``` 上述例子中的执行结果是因为 `then` 的执行会返回一个新的 Promise 对象,并且如果 `then` 执行后返回的仍然是 Promise 对象,那么下一个 `then` 的链式调用会等待该 Promise 对象的状态发生变化后才会调用(能得到这个 Promise 处理的结果)。接下来重点看下 Promise 的错误处理: ```typescript const promise = new Promise<string>((resolve, reject) => { // 下述是异常代码 console.log(a.b.c); resolve('hello'); }); promise .then((result) => { console.log('result: ', result); }) // 去掉 catch 仍然会抛出错误,但不会退出进程终止脚本执行 .catch((err) => { // 执行 // ReferenceError: a is not defined console.error(err); }); setTimeout(() => { // 继续执行 console.log('hello world!'); }, 2000); ``` 从上述示例可以看出 Promise 的错误不会影响其他代码的执行,只会影响 Promise 内部的代码本身,因为 Promise 会在内部对错误进行异常捕获,从而保证整体代码执行的稳定性。Promise 还提供了其他的一些 API 方便多任务的执行,包括 * `Promise.all`:适合多个异步任务并发执行但不允许其中任何一个任务失败 * `Promise.race` :适合多个异步任务抢占式执行 * `Promise.allSettled` :适合多个异步任务并发执行但允许某些任务失败 Promise 相对于 Callback 对于异步的处理更加优雅,并且能力也更加强大, 但是也存在一些自身的缺点: * 无法取消 Promise 的执行 * 无法在 Promise 外部通过 `try...catch...` 的形式进行错误捕获(Promise 内部捕获了错误) * 状态单一,每次决断只能产生一种状态结果,需要不停的进行链式调用 > 温馨提示:手写 Promise 是面试官非常喜欢的一道笔试题,本质是希望面试者能够通过底层的设计正确了解 Promise 的使用方式,如果你对 Promise 的设计原理不熟悉,可以深入了解一下或者手动设计一个。 **Generator** Promise 解决了 Callback 的回调地狱问题,但也造成了代码冗余,如果一些异步任务不支持 Promise 语法,就需要进行一层 Promise 封装。Generator 将 JavaScript 的异步编程带入了一个全新的阶段,它使得异步代码的设计和执行看起来和同步代码一致。Generator 使用的简单示例如下: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 2), 1000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 3), 1000); }); }; // 在 Generator 函数里执行的异步代码看起来和同步代码一致 function* gen(result: number): Generator<Promise<number>, Promise<number>, number> { // 异步代码 const firstResult = yield firstPromise(result) console.log('firstResult: ', firstResult) // 2 // 异步代码 const nextResult = yield nextPromise(firstResult) console.log('nextResult: ', nextResult) // 6 return nextPromise(firstResult) } const g = gen(1) // 手动执行 Generator 函数 g.next().value.then((res: number) => { // 将 firstPromise 的返回值传递给第一个 yield 表单式对应的 firstResult return g.next(res).value }).then((res: number) => { // 将 nextPromise 的返回值传递给第二个 yield 表单式对应的 nextResult return g.next(res).value }) ``` 通过上述代码,可以看出 Generator 相对于 Promise 具有以下优势: * 丰富了状态类型,Generator 通过 `next` 可以产生不同的状态信息,也可以通过 `return` 结束函数的执行状态,相对于 Promise 的 `resolve` 不可变状态更加丰富 * Generator 函数内部的异步代码执行看起来和同步代码执行一致,非常利于代码的维护 * Generator 函数内部的执行逻辑和相应的状态变化逻辑解耦,降低了代码的复杂度 `next` 可以不停的改变状态使得 `yield` 得以继续执行的代码可以变得非常有规律,例如从上述的**手动执行 Generator 函数**可以看出,完全可以将其封装成一个自动执行的执行器,具体如下所示: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 2), 1000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 3), 1000); }); }; type Gen = Generator<Promise<number>, Promise<number>, number> function* gen(): Gen { const firstResult = yield firstPromise(1) console.log('firstResult: ', firstResult) // 2 const nextResult = yield nextPromise(firstResult) console.log('nextResult: ', nextResult) // 6 return nextPromise(firstResult) } // Generator 自动执行器 function co(gen: () => Gen) { const g = gen() function next(data: number) { const result = g.next(data) if(result.done) { return result.value } result.value.then(data => { // 通过递归的方式处理相同的逻辑 next(data) }) } // 第一次调用 next 主要用于启动 Generator 函数 // 内部指针会从函数头部开始执行,直到遇到第一个 yield 表达式 // 因此第一次 next 传递的参数没有任何含义(这里传递只是为了防止 TS 报错) next(0) } co(gen) ``` > 温馨提示:[TJ Holowaychuk ](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Ftj "https://github.com/tj")设计了一个 Generator 自动执行器 [Co](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Ftj%2Fco "https://github.com/tj/co"),使用 Co 的前提是 `yield` 命令后必须是 Promise 对象或者 Thunk 函数。Co 还可以支持并发的异步处理,具体可查看官方的 [ API 文档](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Ftj%2Fco%23arrays "https://github.com/tj/co#arrays")。 需要注意的是 Generator 函数的返回值是一个 Iterator 遍历器对象,具体如下所示: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 2), 1000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 3), 1000); }); }; type Gen = Generator<Promise<number>>; function* gen(): Gen { yield firstPromise(1); yield nextPromise(2); } // 注意使用 next 是继发执行,而这里是并发执行 Promise.all([...gen()]).then((res) => { console.log('res: ', res); }); for (const promise of gen()) { promise.then((res) => { console.log('res: ', res); }); } ``` Generator 函数的错误处理相对复杂一些,极端情况下需要对执行和 Generator 函数进行双重错误捕获,具体如下所示: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { // 需要注意这里的 reject 没有被捕获 setTimeout(() => reject(result * 2), 1000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 3), 1000); }); }; type Gen = Generator<Promise<number>>; function* gen(): Gen { try { yield firstPromise(1); yield nextPromise(2); } catch (err) { console.error('Generator 函数错误捕获: ', err); } } try { const g = gen(); g.next(); // 返回 Promise 后还需要通过 Promise.prototype.catch 进行错误捕获 g.next(); // Generator 函数错误捕获 g.throw('err'); // 执行器错误捕获 g.throw('err'); } catch (err) { console.error('执行错误捕获: ', err); } ``` 在使用 `g.throw` 的时候还需要注意以下一些事项: * 如果 Generator 函数本身没有捕获错误,那么 Generator 函数内部抛出的错误可以在执行处进行错误捕获 * 如果 Generator 函数内部和执行处都没有进行错误捕获,则终止进程并抛出错误信息 * 如果没有执行过 `g.next`,则 `g.throw` 不会在 Gererator 函数中被捕获(因为执行指针没有启动 Generator 函数的执行),此时可以在执行处进行执行错误捕获 **Async** Async 是 Generator 函数的语法糖,相对于 Generator 而言 Async 的特性如下: * 内置执行器:Generator 函数需要设计手动执行器或者通用执行器(例如 Co 执行器)进行执行,Async 语法则内置了自动执行器,设计代码时无须关心执行步骤 * `yield` 命令无约束:在 Generator 中使用 Co 执行器时 `yield` 后必须是 Promise 对象或者 Thunk 函数,而 Async 语法中的 `await` 后可以是 Promise 对象或者原始数据类型对象、数字、字符串、布尔值等(此时会对其进行 `Promise.resolve()` 包装处理) * 返回 Promise: `async` 函数的返回值是 Promise 对象(返回原始数据类型会被 Promise 进行封装), 因此还可以作为 `await` 的命令参数,相对于 Generator 返回 Iterator 遍历器更加简洁实用 举个简单的示例: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 2), 1000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 3), 1000); }); }; async function co() { const firstResult = await firstPromise(1); // 1s 后打印 2 console.log('firstResult: ', firstResult); // 等待 firstPromise 的状态发生变化后执行 const nextResult = await nextPromise(firstResult); // 2s 后打印 6 console.log('nextResult: ', nextResult); return nextResult; } co(); co().then((res) => { console.log('res: ', res); // 6 }); ``` 通过上述示例可以看出,`async` 函数的特性如下: * 调用 `async` 函数后返回的是一个 Promise 对象,通过 `then` 回调可以拿到 async 函数内部 `return` 语句的返回值 * 调用 `async` 函数后返回的 Promise 对象必须等待内部所有 `await` 对应的 Promise 执行完(这使得 `async` 函数可能是阻塞式执行)后才会发生状态变化,除非中途遇到了 `return` 语句 * `await` 命令后如果是 Promise 对象,则返回 Promise 对象处理后的结果,如果是原始数据类型,则直接返回原始数据类型 上述代码是阻塞式执行,`nextPromise` 需要等待 `firstPromise` 执行完成后才能继续执行,如果希望两者能够并发执行,则可以进行下述设计: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 2), 1000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 3), 1000); }); }; async function co() { return await Promise.all([firstPromise(1), nextPromise(1)]); } co().then((res) => { console.log('res: ', res); // [2,3] }); ``` 除了使用 Promise 自带的并发执行 API,也可以通过让所有的 Promise 提前并发执行来处理: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { console.log('firstPromise'); setTimeout(() => resolve(result * 2), 10000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { console.log('nextPromise'); setTimeout(() => resolve(result * 3), 1000); }); }; async function co() { // 执行 firstPromise const first = firstPromise(1); // 和 firstPromise 同时执行 nextPromise const next = nextPromise(1); // 等待 firstPromise 结果回来 const firstResult = await first; console.log('firstResult: ', firstResult); // 等待 nextPromise 结果回来 const nextResult = await next; console.log('nextResult: ', nextResult); return nextResult; } co().then((res) => { console.log('res: ', res); // 3 }); ``` Async 的错误处理相对于 Generator 会更加简单,具体示例如下所示: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { // Promise 决断错误 setTimeout(() => reject(result * 2), 1000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 3), 1000); }); }; async function co() { const firstResult = await firstPromise(1); console.log('firstResult: ', firstResult); const nextResult = await nextPromise(1); console.log('nextResult: ', nextResult); return nextResult; } co() .then((res) => { console.log('res: ', res); }) .catch((err) => { console.error('err: ', err); // err: 2 }); ``` `async` 函数内部抛出的错误,会导致函数返回的 Promise 对象变为 `rejected` 状态,从而可以通过 `catch` 捕获, 上述代码只是一个粗粒度的容错处理,如果希望 `firstPromise` 错误后可以继续执行 `nextPromise`,则可以通过 `try...catch...` 在 `async` 函数里进行局部错误捕获: ```typescript const firstPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { // Promise 决断错误 setTimeout(() => reject(result * 2), 1000); }); }; const nextPromise = (result: number): Promise<number> => { return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 3), 1000); }); }; async function co() { try { await firstPromise(1); } catch (err) { console.error('err: ', err); // err: 2 } // nextPromise 继续执行 const nextResult = await nextPromise(1); return nextResult; } co() .then((res) => { console.log('res: ', res); // res: 3 }) .catch((err) => { console.error('err: ', err); }); ``` > 温馨提示:Callback 是 Node.js 中经常使用的编程方式,Node.js 中很多原生的 API 都是采用 Callback 的形式进行异步设计,早期的 Node.js 经常会有 Callback 和 Promise 混用的情况,并且在很长一段时间里都没有很好的支持 Async 语法。如果你对 Node.js 和它的替代品 Deno 感兴趣,可以观看 Ryan Dahl 在 TS Conf 2019 中的经典演讲 [Deno is a New Way to JavaScript](https://link.juejin.cn/?target=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D1gIiZfSbEAE "https://www.youtube.com/watch?v=1gIiZfSbEAE")。 ## Object.defineProperty 有哪几个参数?各自都有什么作用? ## Object.defineProperty 和 ES6 的 Proxy 有什么区别? > 阅读链接:[基于 Vue 实现一个 MVVM](https://juejin.cn/post/6844904099704471559#heading-23 "https://juejin.cn/post/6844904099704471559#heading-23") - 数据劫持的实现。 ## ES6 中 Symbol、Map、Decorator 的使用场景有哪些?或者你在哪些库的源码里见过这些 API 的使用? ## 为什么要使用 TypeScript ? TypeScript 相对于 JavaScript 的优势是什么? ## TypeScript 中 const 和 readonly 的区别?枚举和常量枚举的区别?接口和类型别名的区别? ## TypeScript 中 any 类型的作用是什么? ## TypeScript 中 any、never、unknown 和 void 有什么区别? ## TypeScript 中 interface 可以给 Function / Array / Class(Indexable)做声明吗? ## TypeScript 中可以使用 String、Number、Boolean、Symbol、Object 等给类型做声明吗? ## TypeScript 中的 this 和 JavaScript 中的 this 有什么差异? ## TypeScript 中使用 Unions 时有哪些注意事项? ## TypeScript 如何设计 Class 的声明? ## TypeScript 中如何联合枚举类型的 Key? ## TypeScript 中 ?.、??、!.、_、** 等符号的含义? ## TypeScript 中预定义的有条件类型有哪些? ## 简单介绍一下 TypeScript 模块的加载机制? ## 简单聊聊你对 TypeScript 类型兼容性的理解?抗变、双变、协变和逆变的简单理解? ## TypeScript 中对象展开会有什么副作用吗? ## TypeScript 中 interface、type、enum 声明有作用域的功能吗? ## TypeScript 中同名的 interface 或者同名的 interface 和 class 可以合并吗? ## 如何使 TypeScript 项目引入并识别编译为 JavaScript 的 npm 库包? ## TypeScript 的 tsconfig.json 中有哪些配置项信息? ## TypeScript 中如何设置模块导入的路径别名?
追风者
2022年3月23日 23:47
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码