异步编程(Promise、Generator、Async与Await)

篇头

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

引子

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

  • 优点:程序执行更安全、更简单
  • 缺点:容易被耗时长的任务卡住,造成程序假死
    在这种单线程的工作模式下,我们为解决以上问题就出现了:同步模式异步模式

同步模式

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

异步模式

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

Promise

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

  • pending: 初始状态,不是成功或失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。
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");
}) 
  • Promisethen方法会返回一个全新的Promise对象
  • 后面的then都是在为上一个的then返回的promise添加状态明确过后的回调
  • 前面的then方法中回调函数的返回值会作为后面then方法回调的参数
  • 如果回调中返回的是Promise, 那么后面的then方法的回调会等待它的结束

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()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容