JS异步编程及进化史

一、什么是异步编程?

首先我们需要了解JS是单线程语言。为了避免出现线程同步的问题。一个人执行一个任务,如果有多个任务,那任务需要排队,然后一个一个去执行。

单线程的优势是更安全更简单,但是缺点也很明显,如果中间某个任务特别耗时,那么就会出现阻塞。所以为了解决这种问题,JS有两种执行任务的模式:

同步模式(Synchronous)和异步模式(Asynchronous)。

同步模式:代码依次执行,后一个任务必须等待前一个任务结束才可以执行。

console.log('global begin')
function bar () {
    console.log('bar task') 
}
function foo () {
    console.log('foo task')
    bar()
}
foo()
console.log('global end')

// global begin
// foo task
// bar task
//global end

异步模式:不会去等待这个任务的结束才开始下一个任务,都是开启过后就立即往后执行下一个任务。耗时函数的后续逻辑会通过回调函数的方式定义。在内部,耗时任务完成过后就会自动执行传入的回调函数。

console.log('global begin')
// 延时器
setTimeout(function timer1 () {
    console.log('timer1 invoke')
}, 1800)
// 延时器中又嵌套了一个延时器
setTimeout(function timer2 () {
    console.log('timer2 invoke')
    setTimeout(function inner () {
        console.log('inner invoke')
    }, 1000)
}, 1000)
console.log('global end')

// global begin
// global end
// timer2 invoke
// timer1 invoke
// inner invoke

异步模式化的难点就是执行顺序的问题。// 这里应该了解调用栈、消息队列和事件循环

二、了解 Promse

回调函数是异步编程方案的根基,但是会出现回调地狱的问题。为了避免这个问题,CommonJs提出了Promise规范,Es6中称为语言规范。

Promise是一个对象,用来表述一个异步任务执行之后是成功还是失败。

// promise基本用法

const promise = new Promise((resolve,reject)=>{
    if (true) {
        resolve(100)  // 执行成功返回resolve
    } eles {
         reject(new Error('promise rejected')) // 执行失败返回reject
    }
})

promise.then((value)=>{
    console.log('resolved',value) // resolve 100
},(error)=>{
    console.log('reject',error) // rejected Error: promise rejected
})

 - 即便promise中没有任何的异步操作,then方法的回调函数仍然会进入到事件队列中排队。

- Promise的本质:本质上也是使用回调函数的方式去定义异步任务结束后所需要执行的任务。这里的回调函数是通过then方法传递过去的

- Promise链式调用

1. 常见误区 - 嵌套使用的方式是使用Promise最常见的误区。要使用promise的链式调用的方法尽可能保证异步任务的扁平化。

2. 链式调用的理解 - promise对象then方法,返回了全新的promise对象。可以再继续调用then方法,如果return的不是promise对象,而是一个值,那么这个值会作为resolve的值传递,如果没有值,默认是undefined - 后面的then方法就是在为上一个then返回的Promise注册回调 - 前面then方法中回调函数的返回值会作为后面then方法回调的参数 - 如果回调中返回的是Promise,那后面then方法的回调会等待它的结束

- Promise异常处理 

1. then中回调的onRejected方法

2. .catch()(推荐) promise中如果有异常,都会调用reject方法,还可以使用.catch() 使用.catch方法更为常见,因为更加符合链式调用。.catch是给整个promise链条注册的一个失败回调

// 使用Promise去封装一个ajax的案例
function ajax (url) {
  return new Promise((resolve, rejects) => {
    // 创建一个XMLHttpRequest对象去发送一个请求
    const xhr = new XMLHttpRequest()
    // 先设置一下xhr对象的请求方式是GET,请求的地址就是参数传递的url
    xhr.open('GET', url)
    // 设置返回的类型是json,是HTML5的新特性
    // 我们在请求之后拿到的是json对象,而不是字符串
    xhr.responseType = 'json'
    // html5中提供的新事件,请求完成之后(readyState为4)才会执行
    xhr.onload = () => {
      if(this.status === 200) {
        // 请求成功将请求结果返回
        resolve(this.response)
      } else {
        // 请求失败,创建一个错误对象,返回错误文本
        rejects(new Error(this.statusText))
      }
    }
    // 开始执行异步请求
    xhr.send()
  })
}

ajax('/api/user.json').then((res) => {
  console.log(res)
}, (error) => {
  console.log(error)
})

- Promise.all

   可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

let wake = (time) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${time / 1000}秒后醒来`)
    }, time)
  })
}

let p1 = wake(3000)
let p2 = wake(2000)

Promise.all([p1, p2]).then((result) => {
  console.log(result)       // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) => {
  console.log(error)
})

// Promise.all获得的成功结果的数组里面的数据顺序和接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。

- Promise.race

  Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  },1000)
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('failed')
  }, 500)
})

Promise.race([p1, p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)  // 打开的是 'failed'
})

-- Promise 执行时序 / 宏任务与微任务

console.log(global start);
setTimeout(()=>{
    console.log('setTimeout')
},0)
Promise.resolve()
.then(()=>{
    console.log('promise')
})
.then(()=>{
    console.log('promise2')
})
console.log('global end')

// global start
// global end
// promise
// promise1
// setTimeout

// 回调函数中的任务称为宏任务
// 微任务:提高整体的响应能力 promise&process.nectTick&MutationObserver

宏任务与微任务运行机制:

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

图例:

三、了解 Generator

generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。

function* foo(x) {
    console.log('start')
    try {
        const res = yield 'foo';
        console.log(res); // bar
        const urls = yield ajax('...');
    } catch(e) {
        console.log(e)
    }
}

// yield 返回关键词

const generator = foo();
const result = generator.next('bar'); // 传入的参数在yield 前可以接收

generator.throw(new Error('Generator error')) // 抛出异常的函数

/** 
next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果done为true,则value就是return的返回值。

当执行到done为true时,这个generator对象就已经全部执行完毕,不要再继续调用next()了。
**/


// generator异步方案 -- 递归执行异步调用
function handleResult(result) {
    if(result.done) return
    result.value.then(data => {
        handleResult(generator.next(data))
    },error => {
        generator.throw(error)
    })
}

// 了解co异步方案

四、Async / Await 语法糖  - 语言层面的异步编程标准

async function foo(x) {
    console.log('start')
    try {
        const res = await ajax('...url11');
        const urls = await ajax('...url2');
    } catch(e) {
        console.log(e)
    }
}

// await 只能出现在async里面
// await后面接一个会return new promise的函数并执行它
const promise = main();
promise.then(()=>{
    console.log('all complete')
})

参考链接: 

https://blog.csdn.net/wu_xianqiang/article/details/105837869 (彻底理解JS是单线程的)

https://www.cnblogs.com/lelexiu/p/10095718.html (理解调用栈、事件循环、消息队列)

猜你喜欢

转载自blog.csdn.net/qq_42269433/article/details/114939867
今日推荐