异步编程

485 阅读7分钟

目前主流的JavaScript环境都是以单线程模式去执行代码。JavaScript是运行在浏览器端的脚本语言,为了去实现页面的动态交互,而实现页面交互的核心是dom操作,如果在同一时间,不同的线程对dom进行不同的操作,例如一个线程添加dom,一个线程删除dom,这个时候浏览器会不知该如何处理,为了避免这种情况的出现,所以js只能按照顺序从上到下执行,这种从上到下按顺序执行就是同步任务。单线程的JavaScript语言无法同时处理大量耗时任务,这就需要采用异步模式去编写代码。

同步模式与异步模式

同步模式

同步模式指的是代码中的任务依次执行,后一个任务必须等前一个任务执行完毕后才能执行

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

异步模式

异步模式:不会等待这个任务的结束才开始下一个任务,对于耗时操作都是开启过后就立即往后执行下一个任务,后续逻辑一般会通过回调函数的方式定义。帮助单线程的js语言同时处理大量的耗时任务。
缺点:代码执行顺序混乱

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

回调函数--所有异步编程方案的根基

回调函数:由调用者定义,交给执行者执行的函数

function foo(callback){
    setTimeOut(fucntion(){
        callback()
    },1000)
}

foo(function(){
    console.log('这就是一个回调函数')
    console.log('调用这定义这个函数,执行者执行这个函数')
    console.log('其实就是调用者告诉执行者异步任务结束后做什么')
})

Promise

promise 就是一个类,在执行这个类的时候需要一个执行器进去,执行器会立即执行

Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。

Promise 对象特点

1、**对象的状态不受外界影响。**Promise 对象代表一个异步操作,有三种状态:

  • pending: 初始状态,不是成功或失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

2、**一旦状态改变,就不会再变,任何时候都可以得到这个结果。**Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。

promise的使用案例

// promise方式的ajax

function ajax(url){
    return new Promise(function(resolve,reject){
        var xhr = new XMLHttpRequest()
        xhr.open('GET',url)
        xhr.responseType = 'json'
        xhr.onload = function(){
            if(this.status==200){
                resolve(this.response)
            }else{
                reject(new Error(this.statusText))
            }
        }
        xhr.send()
    })
}

ajax('/api/user.json').then(function(res){
    console.log(res)
},function(err){
    console.log(err)
})



/*
    then方法内部做的事情是判断状态,如果状态是成功,调用成功回调的函数,如果失败,调用失败的回调函数
    then方法是被定义在原型对象中
    同一个promise对象下面的then方法是可以被调用多次的,所以then要返回一个全新的promise对象    then方法是可以被链式调用的,后面then方法的回调函数拿到值的是上一个then方法的回调函数的返回值
    如果回调中返回的是Promise,那后面then方法的回调会等待它的结束
    .then( ) 里面的实参应该是函数。如果不是函数, 就无视它。
*/
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)

// 输出结果
1

promise链式调用

function ajax(url){
    return new Promise(function(resolve,reject){
        var xhr = new XMLHttpRequest()
        xhr.open('GET',url)
        xhr.responseType = 'json'
        xhr.onload = function(){
            if(this.status==200){
                resolve(this.response)
            }else{
                reject(new Error(this.statusText))
            }
        }
        xhr.send()
    })
}

ajax('/api/user.json').then(function(res){
    console.log(111)
    return ajax('/api/user.json')
}).then(function(res){
    console.log(222)
    console.log(res)
    return ajax('/api/user.json')
}).then(function(res){
    console.log(333)
    return ajax('/api/user.json')
}).then(function(res){
    console.log(444)
    return 'foo'
}).then(function(res){
    console.log(555)
    console.log(res)
})


//输出信息
111
222
{users:'/api/users.json',posts:'/api/users.json'}
333
444
555
foo

Promise对象的then方法返回一个全新的Promise对象

后面的then方法就是在为上一个then返回的Promise注册回调

前面的then方法中回调函数的返回值会作为后面then方法回调的参数

Promise静态方法

Promise.resolve

Promise.resolve('foo').then(function (value) {
    console.log(value)
})
等价于
new Promise(function (resolve, reject) {
    resolve('foo')
})

// 如果传入的是一个 Promise 对象,Promise.resolve 方法原样返回
var promise = ajax('/api/users.json')
var promise2 = Promise.resolve(promise)
console.log(promise === promise2) // true

// 如果传入的是带有一个跟 Promise 一样的 then 方法的对象,
// Promise.resolve 会将这个对象作为 Promise 执行
Promise.resolve({
    then: function (onFulfilled, onRejected) {
        onFulfilled('foo')
    }
}).then(function (value) {console.log(value)})

Promise.reject

// Promise.reject 传入任何值,都会作为这个 Promise 失败的理由
Promise.reject('anything').catch(function (error) { console.log(error) })

Promise.all-等待所有任务结束

同步执行多个promise,当所有的promise都成功结束了,才会成功结束,若是有一个失败,就是以失败告终

var promise = Promise.all([
    ajax('/api/users.json'),
    ajax('/api/posts.json')
])
promise.then(function (values) {
    console.log(values)
}).catch(function (error) {
    console.log(error)
})

Promise.race-只会等待第一个结束的任务

// Promise.race 实现超时控制
const request = ajax('/api/posts.json')
const timeout = new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error('timeout')), 500)
})
Promise.race([ request, timeout ]).then(value => {
    console.log(value)
}).catch(error => {
    console.log(error)
})

Promise执行时序

// 微任务 直接在当前任务结束过后立即执行,提高整体的响应能力
console.log('global start')

// setTimeout 的回调是宏任务,进入回调队列排队
setTimeout(() => { console.log('setTimeout') }, 0)

// Promise 的回调是微任务,本轮调用末尾直接执行
Promise.resolve().then(() => {
    console.log('promise')
}).then(() => {
    console.log('promise 2')
}).then(() => {
    console.log('promise 3')
})
console.log('global end')

//输出
global start
global end
promise
promise 2
promise 3
setTimeout

目前绝大多数的异步调用都是作为宏任务执行,但Promise、MutationObserver、process.nextTick作为微任务执行。

Generator

Generator(生成器函数)就是在普通函数基础上用*去声明一个生成器对象,再通过调用.next()去执行这个函数。函数内部可以通过yield去返回一个值,在next方法返回对象中拿到这个值,返回对象中还包含一个done属性,表示这个生成器是否执行完了。如果在next方法中传入一个参数,传入的参数会作为yield语句的返回值。如果外部调用throw方法,也会让生成器函数往下执行,作用是抛出一个异常。

function * foo () {
    console.log('start')
    const res = yield 'foo'
    console.log(res)
}
const generator = foo()
const result = generator.next()
console.log(result)
generator.next('bar')
//输出
start
{value:'foo',done:false}
bar


function * foo () {
    console.log('start')
    try {
        const res = yield 'foo'
        console.log(res)
    } catch (e) {
        console.log(e)
    }
}
const generator = foo()
const result = generator.next()
console.log(result)
generator.throw(new Error('Generator error'))

//输出
start
{value:'foo',done:false}
Error:Generator error

Async Await

async function main () {
    try {
        const users = await ajax('/api/users.json')
        console.log(users)
        const posts = await ajax('/api/posts.json')
        console.log(posts)
        const urls = await ajax('/api/urls.json')
        console.log(urls)
    } catch (e) {
        console.log(e)
    }
}
const promise = main()
promise.then(() => { console.log('all completed') })

EventLoop、消息队列都是做什么的?

EventLoop(事件循环机制)负责监听调用栈和消息队列,一旦调用栈所有任务都结束了,事件循环就会从消息队列中取出第一个回调函数,加入消息队列中。

消息队列用来处理异步任务。每当出现异步调用事件时都会将其入队,执行完毕后再由任务队列通知主线程,让JS引擎接管此事件。

什么是宏任务?什么是微任务?

回调队列中的任务是宏任务,一切事件(同步代码块、setTimeout、setInterval等)都是宏任务。而微任务则是直接在当前任务结束过后立即执行的任务,包括Promise、MutationObserver、process.nextTick。顺序的话还是同步事件优先级最高,而这些异步事件其次。当执行宏任务时遇到Promise等,会创建微任务(.then()里的回调),并加入微任务队列队尾。