篇二:大前端基础之JavaScript异步编程笔记

258 阅读5分钟

文章输出主要来源:拉勾大前端高新训练营(链接)。小哥哥小姐姐请不要嫌弃啰嗦,下面肯定都是干货。

1. 单线程

原因: 最初设计为浏览器脚本,用于操作DOM,为了避免线程同步的不问题,设计之初就采用了单线程模式。

**单线程:**执行代码的线程只有一个。

代码执行模式:

  • 同步模式
  • 异步模式:解决同步耗时任务

2. 同步模式(Synchronous)

同步代码按顺序依次执行,依次压如调用栈进行执行。

**问题:**同步执行的代码遇到耗时操作会阻塞后续代码,造成页面卡死现象。

**解决方案:**耗时任务使用异步模式执行。

3. 异步模式(Asynchronous)

特点:

  1. 不会等待代码执行
  2. 开启任务之后会立刻继续向后执行
  3. 后续逻辑通常使用回调函数方式定义

**缺点:**代码执行顺序比较混乱

JavaScript异步工作图:

注意事项

JavaScript的执行是单线程的,但是浏览器并不是单线程,JavaScript调用的某些浏览器api并不是单线程的(例如计时器,拥有专门的线程管理)。

4. 回调函数

回调函数是异步实现最本质的机制。

缺点:

大量回调嵌套会导致回调地域

5. Promise

Promise状态图

Promise的状态一经确定就无法再做修改。

Promise的定义与使用

const promise = new Promise(function(resolve, reject) {
  console.log('aa')
  resolve(100)

  // reject(new Error('promise rejected'))
});

promise.then(value => {
  console.log('resolved', value);
}, error => {
  console.error('rejected ', error);
})

console.log('end');

通过new Promise创建promise实例,resolve钩子返回成功结果,reject钩子返回失败结果,通过promise.then中接收状态明确后的回调函数,可以接收的两个函数,第一个为成功回调,第二个为失败回调(可省略),使用.catch()接收失败的回调。promise也是异步任务(微任务),定义promise时会先执行Promise传递进去的函数中的同步任务,resolve与reject的回调为异步任务。

如上打印结果为aa,end,resolved 100

解决回调地域

借助promise的链式操作,尽可能将原来嵌套的回调变为扁平化处理。

特点:

  • promise对象.then返回的对象也是一个promise,且是一个新的promise
  • 后面的then方法为前面的then方法返回的promise结果注册回调
  • then方法返回的值会成为下一个then方法的参数
  • then方法若返回一个promise,后面的then方法会等待它的结束

异常处理

promise失败或出现异常会执行onRejected注册的回调。

失败回调指定方式

  1. then方法的第二个参数,缺点:无法捕获到本轮then方法以及下层的异常
  2. catch方法:推荐使用

Promise静态方法

  1. Promise.resolve:创建promise成功的方法

    • 可以将普通值快速转换为promise对象

    • 如果传递的为promise,会将这个promise原样返回

    • 如果传递的是一个对象,并且带有then方法,第一个参数为onFulfilled,第二个参数为onRejected,也可以被当做promise继续向下执行。这种对象实现了thenable的接口。

  2. Promise.reject:创建promise失败的方法

  3. Promise.all([]):将多个promsie合并为一个promise,所有promise均成功它才会成功

  4. Promise.race([]): 多个promsie中有任意一个成功,则成功

Promise执行时序

异步任务可以作为宏任务去宏任务异步进行排队,等待本轮宏任务执行完毕再进行执行。

也可以作为当前宏任务的临时附属任务,加入到微任务队列(微任务可以提高整体响应能力),本轮同步任务执行完毕后,先执行本轮微任务队列中的任务,再从宏任务队列中取出任务执行。

promise的回调会作为微任务来执行。

目前大部分异步任务都会作为宏任务执行。

微任务目前有:

  1. Promise
  2. MutationObserver对象
  3. Node中的process.nextTick

6. Generator异步方案

生成器定义方式

// 通过给函数添加*定义生成器函数
function * foo() {
  console.log('start');
}
// 调用一个生成器函数并不会立刻执行函数,而是会创建一个生成器对象
const generator = foo();
// 直到手动调用生成器的.next()方法,函数体才会开始执行 
generator.next();

yield

function * foo() {
  console.log('start');
	// 函数体内可以随时通过yield关键词向外返回一个值,yield关键词并不会像return一样结束函数的执行,只会暂停函数的执行,直到外界继续调用.next()方法,函数才会接着yield后面开始继续执行
  yield 'foo';
}

const generator = foo();

// 上方yield返回的值,可以通过next()返回的对象的value中拿到,除此之外该结果还有个done属性表示生成器是否执行完毕
const result = generator.next();

console.log(result)

传递参数

function * foo() {
  console.log('start');
	// 2. 在yield中的返回值中会接收到传入的参数
  const res = yield 'foo';
  console.log(res); // bar
}

const generator = foo();

const result = generator.next();
console.log(result)
// 1. 如果在.next()方法中传递一个参数
generator.next('bar');

捕获异常

function * foo() {
  try {
    console.log('start');
    const res = yield 'foo';
    console.log(res);
  } catch(exp) {
    // 2. 可以通过catch进行异常的捕获
    console.log(exp);
  }
}

const generator = foo();
const result = generator.next();
console.log(result)
generator.next('bar');

// 1. 如果通过throw抛出一个异常
generator.throw(new Error('error'))

7. Async/Await

本质,genreator语法糖,相比generator优势为不需要额外写执行器。

await目前只能在async函数中使用,在全局使用尚在开发中。