JavaScript异步编程

169 阅读7分钟

JavaScript是单线程的语言,脚本内部只有一个线程执行代码,多个任务就按顺序依次执行。优点是安全和简单,缺点是遇到耗时任务就要等待耗时任务完成后,才能执行下面的操作。

为了解决耗时任务的等待,JavaScript将任务的执行分为两种模式:同步模式和异步模式

同步模式:任务会按顺序依次执行,当遇到大量耗时任务,后面的任务就会被延迟,这种延迟称为阻塞,阻塞会造成页面卡顿

异步模式:不会等待耗时任务,遇到异步任务就开启后立即执行下一个任务,耗时任务的后续逻辑通常通过回调函数来定义执行,代码执行顺序混乱

console.log(1)
setTimeout(function() {
    console.log(4)
}, 2000)
setTimeout(function() {
    console.log(3)
}, 1000)
console.log(2)

当遇到异步任务时创建任务,主线程(执行栈)继续执行后面的操作,另一个线程由js的执行环境提供,异步任务会由另一个线程执行。当主线程执行完本轮任务后,Event loop会检查消息队列中是否有回调执行。异步任务执行完后,会将异步任务的回调函数会按照执行完成顺序,放入消息队列中,当消息队列变化后,Event loop会取出消息队列头一个回调放入主线程中执行,循环执行完消息队列的回调,如果再遇到异步任务重复这个过程。

回调函数

回调函数是异步编程的基本

回调函数可以理解为一件想要去做的事情,由调用者定义好函数,交给执行者在某个时机去执行,把需要执行的操作操作放在函数里,将函数传入给执行者执行

Promise

是一种用来解决回调函数嵌套的规范,在ES2015中被标准化为规范

Promise是一个对象,用来表示一个异步任务执行的状态,有三种状态

  • Pending:开始是等待状态
  • Fulfilled:成功的状态,会触发onFulfilled
  • Rejected:失败的状态,会触发onRejected

成功或者失败的状态一旦确认,就不可能再改变

const promise = new Promise(function(resolve, reject) {
    // 这里是同步代码
    // resolve执行表示异步任务成功
    // reject执行表示异步任务失败
    // resolve和reject最终只能执行一个
    resolve(100)
    // reject(new Error('reject')) // 失败
})
promise.then(function() {
    // 成功的回调
}, function () {
    // 失败的回调
})

const ajax = function (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('aaa').then(function (res) {
      console.log(res)     
  }, function (err) {
    console.log(err)
})

Promise的链式调用

Promise对象调用then方法后会返回一个新的Promise对象,这个新的Promise对象可以继续调用then实现链式调用

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

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

return 后面可以是Promise对象,普通值(自动包装成Promise resolve 回调传入普通值),不写(自动包装成Promise resolve 回调传入null)

return 返回的如果是Promise对象就会等待Promise执行结束

链式调用的目的是为了解决回调函数嵌套的问题

new Promise((resolve, reject) => {
    resolve()
})
.then()
.then()
.then()

// 回调嵌套
setTimeout(() => {
    console.log('111')
    var a = 10
    setTimeout(() => {
        console.log('222')
        var b = 10
	setTimeout(() => {
            console.log('333')
            var c = 10
	    console.log(a + b + c)
        }, 1000)
    }, 1000)
}, 1000)
//promise链式调用
new Promise((resolve,reject) => {
	setTimeout(() => {
	    console.log('111')
	    var a = 10
	    resolve(a)
	}, 1000)
}).then((res) => {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('222')
            var b = 20
            resolve(res + b)
        }, 1000)
    })
}).then((res) => {
    setTimeout(() => {
        console.log('333')
        var c = 30
        console.log(res + c)
    }, 1000)
})

Promise异常处理

then方法中第二个参数回调(onRejected)就是Promise reject执行,除了then第二个参数外,还可以用catch来捕获reject

链式调用中当前then方法的onRejected不能捕获当前then方法中执行抛出的异常,因为当前then的onRejected是为上一个then,这时出现异常要用catch来捕获

catch最后在Promise链式调用的最后加上,可以捕获整个链式调用中出现的错误,避免then中缺少onRejected回调,导出异常没有被捕获

let promise1 = new Promise(function (resolve, reject) {
    reject(new Error('reject'))
})
// 两种方式都可以
promise1.then(() => {}, (err) => console.log(err))
promise1.then(() => {}).catch(err => console.log(err))

let promise2 = new Promise(function (resolve, reject) {
    resolve()
})

promise2.then(() => {
	return new Promise(function (resolve, reject) {
        throw new Error('error')
    })
}, 
// onRejected不能捕获当前then返回的Promise的回调
(err) => console.log(err))
// catch可以捕获
promise2.then(() => {
	return new Promise(function (resolve, reject) {
        throw new Error('error')
    })
}).catch(err => console.log(err))

Promise静态方法

Promise.resolve():返回一个Promise对象

如果传入是普通值,包装成Promise对象,resolve回调传入的值

传入一个Promise对象,返回传入的Promise对象

传入一个有 接受两个回调参数(onFulFilled,onRejected)的then方法的普通对象,会返回一个新的包装后的Promise对象,then方法会接受到原来对象调用onFulFilled和onRejected值

Promise.rejcet():返回一个Promise对象

无论传入什么参数都会作为reject的失败原因

Promise.all():返回一个Promise对象

传入一个数组,数组的元素是一个个Promise对象,会执行执行Promise对象,等待数组里面所有Promise对象完成后,返回的Promise对象才完成;如果全部Promise成功后拿到的结果是一个数组,数组里的值时对应每个Promise对象的执行结果;如果有其中一个执行失败就会得到一个失败的原因 

Promise.race():返回一个Promise对象

传入一个数组,数组的元素是一个个Promise对象,会执行执行Promise对象,和Promise.all不同,Promise.race只会等待第一个先完成Promise对象返回,第一个返回的结果就是返回新的Promise对象结果

Promise回调执行时序

Promise回调是作为微任务,微任务会在当前宏任务执行完后立即执行,下一个宏任务之前

console.log('start')
// setTimeout是宏任务 在当前宏任务之后
setTimeout(() => {
    console.log('setTimeout')
}, 0)
Promise.resolve()
// then的回调是微任务 在当前宏任务之后 下一个宏任务之前
.then(() => {
    console.log('promise 1')
})
.then(() => {
    console.log('promise 2')
})console.log('end')
// start
// end
// promise 1
// promise 2
// setTimeout

Generator生成器函数

function * foo () {
    console.log('start')
    try {
        const res = yield 'foo'
        // 得到next()传入的值  bar
        console.log(res)    } catch(e) {
        console.log(e) // error
    }
}
const generator = foo()
// 调用generator.next() 会执行到yield的位置
// console.log('start')const result = generator.next()
// next 返回一个对象 { value: 'foo', done: false}
// value 是 yield 后面的值  done 表示是否generator执行完成
console.log(result)
// next可以传值 传入的值会作为 上一个yield的返回值
generator.next('bar')
// 调用throw可以在generator抛出异常
generator.throw(new Error('error'))

生成器执行器

function * main () {
    try {
        const user = yield ajax('user')
        console.log(user)
        const posts = yield ajax('posts')        console.log(posts)
        const url = yield ajax('url')        console.log(url)
    } catch(e) {
        console.log(e)
    }
}
const g = main()
function handleResult (result) {
    if (result.next) {
        // 生成器执行结束
        return
    } else {
        // ajax 返回的Promise对象
        result.value.then(data => {
            // 将当前执行结果data传回给生成器函数 再执行
            handleResult(g.next(data))
        }, error => {
            // Promise执行失败 生成器throw让生成器捕获异常
            g.throw(error)
        })
    }
}
handleResult(g.next())

Async/awit 语法糖

Async和awit是generator生成器函数的语法糖,简化了生成器函数的执行器,并且会返回一个Promise对象

awit只能用在async的函数里面

// 将生成器函数的 * 去掉  在function前面加async
async function main () {
    try {
        // 将yield换成awit
        const user = awit ajax('user')
        console.log(user)
        const posts = awit ajax('posts')
        console.log(posts)
        const url = awit ajax('url')
        console.log(url)
    } catch(e) {
        console.log(e)
    }
}
// 会自动执行
// 执行效果和generator生成器函数一样效果
// 并且返回一个Promise对象
const promise = main()
// 执行完后调用then的回调
promise.then()