异步编程(Promise、Generator、Async与Awa

2021-03-17  本文已影响0人  贰玖是只猫

篇头

我们工作或面试中,经常遇到这些问题:如何更编写更优美的异步代码?你了解promise, async/await吗?知道他们的执行顺序吗?那么接下来我们将从浅入深逐步吃透这些问题。

引子

众所周知 Javascript 是采用的单线程的工作模式?那么为什么会用这种模式呢?
很重要的一点是因为我们页面交互的合适是操作DOM,为避免多线程可能会产生的线程同步问题,因为采用的是单线程工作模式。

同步模式

大多数任务都会异常同步模式执行,任务会一次进入任务的调用栈依次执行,执行后推出调用栈,这里我们不过多展开。

异步模式

一般多是耗时长的任务,开启后就立即执行下个任务,而自己本身的任务是已回调函数的方式去处理后续逻辑。这是我们解决单线程模式缺点的关键。

Promise

Promise 对象代表一个异步操作,有三种状态:

const promise = new Promise((resolve,reject) => {
   rej(new Error("promise rejected"))
})

promise.then(val => {
   console.log("resolve", val);
}).catch(err => {
   console.log("reject", err);
}) 
console.log("end")
// end
// eject Error: promise rejected

then方法的参数是函数,如果不是函数可以直接无视

模拟Ajax

function ajax(url) {
    return new Promise((resolve,reject) => {
        let 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("package.json").then(res => {
    console.log(res)
})

由此我们可以看出promise的本质就是已回调函数的方式去定义异步任务结束之后需要执行的任务
但是好多刚开始学习用promise的同学经常会陷入一个误区,一个异步任务在另一个异步任务回调结束后执行仍然使用了嵌套,这就背离了我们使用promise的初衷。那么解决这个问题的方式就是利用promise链式调用

Promise的链式调用

const promise = new Promise((resolve,reject) => {
    resolve()
})

promise.then(() => {
    return 1;
}).then((res) => {
    console.log(res);
// 输出1
}).then(() => {
    console.log("3");
}) 

Promise的异常处理

常见的异常处理

//第一种
promise.then(resolve => {
    console.log("resolve", resolve);
}, reject => {
    console.log("reject", reject);
})
//第二种
promise.then(resolve => {
    console.log("resolve", resolve);
}).catch(err => {
    console.log("reject", err);
})

\color{red}{unhandledrejection} 是挂载在全局上,用来处理代码中没有被手动捕获的异常,虽说提供了该方法,但还是建议全手动捕获可能出现的异常

浏览器中的使用方式:

window.addEventListener("unhandledrejection", event => {
    const {reason, promise} = event;
    //reason => Promise 失败的原因, 一般是个对象
    //promise => 出现异常的 Primose对象
    event.preventDefault()
},false)

node环境下的使用方式:

process.on("unhandledRejection", (reason, promise) => {
    //reason => Promise 失败的原因, 一般是个对象
    //promise => 出现异常的 Primose对象
})

Promise的静态方法

resolve

Promise.resolve("1")
    .then(val => {console.log(val)})

new Promise((resolve,reject) => {
    resolve("1")
})
//两者等价
let p1 = new Promise((resolve,reject) => {
    resolve("1")
})
let p2 = Promise.resolve(p1)

console.log(p1 === p2)
// true

由此可见将一个Promise对象包裹在另一个Promise对象里面返回的还是最开始的Promise

初此之外还一个特殊的点,如果Promise的参数是一个包含着如下的一个then的对象,那么也可以当做一个回调去执行

Promise.resolve({
    then: (onFulfilled,onReject) => {
        onFulfilled("test 1")
    }
}).then(val => {
    console.log(val)
})
// test 1

reject

同理

Promise.reject("err").catch(err => console.log(err))

Promise 并行执行

我们常见的场景是一个页面加载时需要同时请求多个接口,而接口之间又不相互关联,假如依次串行请求明显时间会很长,那么我们需要将其并行执行。
那么如何知道并行的请求都已完成返回的时间点呢?那么需要我们利用到Promise.all()

Promise.all

Promise.all([promise1, promise2])
    .then(res => {
        //res 是一个返回值的数组
    })
    .catch(err => {
        //只要有一个promise 异常
    })

Promise.race

Promise.all([promise1, promise2])
    .then(res => {
        //等待第一个promise结束
    })

Promise的执行时序 (宏任务、微任务)

宏任务

大部分异步调用都是宏任务

微任务

常见微任务: Promise & MutationObserver & process.nextTick(node环境下)

Generator 异步方案

Generator(生成器函数)是ES2015被提出来的, 下面我们先回顾一下

function * foo() {
    console.log("start")

    try{
        let val = yield "foo"
        console.log(val)
    } catch(e) {
        console.log(e)
    } 

}

const generator = foo() //没有立即执行

const result1 = generator.next() //会执行foo() 直到遇到关键字 yield为止
console.log(result1)
//start
//{value: "foo", done: false} 

const result2 = generator.next("bar") //刚才yield继续往下执行, next里的参数可以复制给 yield前边的变量
console.log(result2)
// bar

generator.throw(new Error("generator error"))

那么接下来我们可以利用Generator中这一特性结合Promise使用

function * main() {
    const json = yield ajax("package.json") //那么我们这里就近似一种同步的书写方式
    console.log(json)
}
const g = main();
const result = g.next();

result.value.then(data => {
    g.next(data)
})

为避免多个promise造成代码的堆积,我们可以利用一下递归, 并且再做一下封装

function * main() {
    const json1 = yield ajax("package.json")
    console.log(json1)
    const json2 = yield ajax("package.json")
    console.log(json2)
    const json3 = yield ajax("package.json")
    console.log(json3)
}
function co(generator) {
const g = main();

function loopHandle(result) {
    if (result.done) return
    result.value
        .then(data => loopHandle(g.next(data)), err => g.throw(err))
}

loopHandle(g.next())
}

co(main)

Aysnc/Await

aysnc/await是generator的语法糖

async function  main() {
    const json1 = await ajax("package.json")
    console.log(json1)
    const json2 = await ajax("package.json")
    console.log(json2)
    const json3 = await ajax("package.json")
    console.log(json3)
}

main()
上一篇下一篇

猜你喜欢

热点阅读