异步的发展历程(小白版)

本文以浅显易懂的语言介绍了异步编程的发展,从回调函数、Promise到Generator和async/await。阐述了它们如何解决回调地狱问题,强调了async/await在简化异步代码上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

因为自己淋过雨,所以想给别人撑把伞。

在这一节,因为主要目标对象是刚学异步的小白同学,所以笔者打算用更通俗的语言来简单梳理一下这方面的内容。😋

同步与异步

既然要谈异步的发展历程,这里当然也要提到一下同步和异步的概念了

什么是同步?

同步就是后一个任务等待前一个任务执行完毕后,再执行,执行顺序和任务排列顺序一致

下面就是同步

console.log('hello 0')

console.log('hello 1')

console.log('hello 2')

// hello 0
// hello 1
// hello 2 

什么是异步?

如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。

setTimeout(() => {console.log('hello 0')
}, 1000)

console.log('hello 1')

// hello 1
// hello 0 

我们为什么要引入异步这个概念呢?

引入一个新的概念当然是为了解决一个特定的问题的。

比如我们加载页面,如果我们需要获取后台数据来渲染到页面上,但是下面还有好多要执行的操作,也许网络延迟,我们这个没获取到,后面的代码是执行不了的,那么页面就会白屏,非常影响用户体验。而异步则不会,我们不会等待异步代码的之后,继续执行异步任务之后的代码。

通俗解释

就好比你追一个女孩子,追了人家好几年了,但人家一直对你不温不火,没有一个具体的答复。所以这个时候就有人会劝你:不要为了一颗树木而放弃整片森林呀。不能在一棵树上吊死,而应该继续走下去。 (有些不严谨😁,因为太俗了)

回调函数 Callback

什么是回调函数

MDN的文档中,对callback()的定义为:

被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。

简单理解:

函数a的参数为函数b,当函数a执行完之后再去执行b

可以通俗地认为:做完函数a的事情,再去做函数b的事情

比如下面:

 console.log('a');console.log('b');console.log('c');setTimeout(() => {console.log('我饭卡拿到了!')},2000)// 异步操作console.log('d');console.log('e'); 

我们可以规定在2000 ms后,再去执行callback函数

用回调函数的方法来进行异步开发好处就是简单明了,容易理解

回调函数的缺点, 用一个小的实例来说明一下:

 setTimeout(function () {//第一层console.log(111);setTimeout(function () {//第二程console.log(222);setTimeout(function () { //第三层console.log(333);}, 1000)}, 2000)}, 3000) 

虽然这种写法解决我们异步任务的代码按顺序执行但是,这种回调函数的层层嵌套,会造成回调地域

这段代码用了三层嵌套请求,就已经让代码变得混乱不堪,所以,我们还需要解决这种嵌套调用后混乱的代码结构。

这段代码之所以看上去很乱,主要原因

1.异步回调嵌套会导致代码可读性变得非常差,并且不方便统一处理错误
2.每一层的执行都依赖于上一层的结果 ,这种嵌套函数的方式耦合性太高,一旦有所改动,就要全改
3.用try catch抛出错误时,不知道哪里出错了

Promise

中文翻译过来就是承诺,意思是在未来某一个时间点承诺返回数据给你。它是js中的一个原生对象,是一种异步编程的解决方案,可以替换掉传统的回调函数解决方案。

如果你还没用过 Promise,可以先读一下阮一峰老师的这篇文章:ES6 Promise对象

这里简单介绍一下:

Promise对象只有三种状态:

1.pendding: 初始状态,既不是成功,也不是失败状态。
2.fulfilled: 意味着操作成功完成。
3.rejected: 意味着操作失败。

Promise 提供了可以链式调用的 then 方法,允许我们在执行完上一步操作后(Promise 从 penddingfulfilled 状态的时候)再去调用then方法

Promise的状态只能由内部改变,并且只可以改变一次。

那么我们看看Promise是如何解决回调地狱问题的,以readFile 为例(先读取A文本内容,再根据A文本内容读取B再根据B的内容读取C)。

function read(url) { return new Promise((resolve, reject) => { fs.readFile(url, 'utf8', (err, data) => { if(err) reject(err); resolve(data); }); });
}
read(A).then(data => { return read(B);
}).then(data => { return read(C);
}).then(data => { return read(D);
}).catch(reason => { console.log(reason);
}); 

从这段 Promise 代码可以看出来,使用 promise.then 也是相当复杂,虽然整个请求流程已经线性化了,但是代码里面包含了大量的 then 函数,使得代码依然不是太容易阅读。

Generator

在进入到async/await之前我们还得了解Generator(生成器)这个概念

我们先来看看什么是生成器函数?

生成器函数是一个带星号(*)函数,而且是可以暂停执行恢复执行的。

我们可以看下面这段代码:

 function* genDemo() {console.log("开始执行第一段")yield 'generator 2'console.log("开始执行第二段")yield 'generator 2'console.log("开始执行第三段")yield 'generator 2'console.log("执行结束")return 'generator 2'
}

console.log('main 0')
let gen = genDemo()
console.log(gen.next().value)
console.log('main 1')
console.log(gen.next().value)
console.log('main 2')
console.log(gen.next().value)
console.log('main 3')
console.log(gen.next().value)
console.log('main 4') 

执行上面这段代码,观察输出结果,你会发现函数 genDemo 并不是一次执行完的,全局代码和 genDemo 函数交替执行。其实这就是生成器函数的特性,可以暂停执行,也可以恢复执行。

下面我们就来看看生成器函数的具体使用方式

1.在生成器函数内部执行一段代码,如果遇到 yield 关键字,那么 JavaScript 引擎将返回关键字后面的内容给外部,并暂停该函数的执行。
2.外部函数可以通过 next 方法恢复函数的执行。

关于函数的暂停和恢复这里就不涉及了,因为这里只想讲个大概的印象。

async/await

虽然生成器已经能很好地满足我们的需求了,但是程序员的追求是无止境的,这不又在 ES7 中引入了 async/await,这种方式能够彻底告别生成器,实现更加直观简洁的代码。

async/await 的优点是代码清晰,不用像 Promise 写很多 then 链,就可以处理回调地狱的问题。并且错误可以被try catch

仍然以上文的readFile (先读取A文本内容,再根据A文本内容读取B再根据B的内容读取C) 为例,使用 async/await 来实现:

const fs = require('fs');
const bluebird = require('bluebird');
const readFile = bluebird.promisify(fs.readFile);

async function read() {await readFile(A, 'utf-8');await readFile(B, 'utf-8');await readFile(C, 'utf-8');//code
}

read().then((data) => {//code
}).catch(err => {//code
}); 

小结

异步最早的解决方案是回调函数,如事件的回调,setInterval/setTimeout中的回调。但是回调函数有一个很常见的问题,就是回调地狱的问题;

为了解决回调地狱的问题,社区提出了Promise解决方案,ES6将其写进了语言标准。Promise一定程度上解决了回调地狱的问题,但是Promise也存在一些问题,如错误不能被try catch,而且使用Promise的链式调用,其实并没有从根本上解决回调地狱的问题,只是换了一种写法。

ES6中引入 Generator 函数,Generator是一种异步编程解决方案,Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权,Generator 函数可以看出是异步任务的容器,需要暂停的地方,都用yield语句注明。但是 Generator 使用起来较为复杂。

ES7又提出了新的异步解决方案:async/await,async是 Generator 函数的语法糖,async/await 使得异步代码看起来像同步代码,异步编程发展的目标就是让异步逻辑的代码看起来像同步一样。

回调函数 —> Promise —> Generator —> async/await.

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值