摘要:前言对于这门语言,其实我更喜欢称它为,从一开始我们就已经涉及到异步编程,但是多数开发者从来没有认真思考过自己程序中的异步,到底是怎么实现的,以及为什么会出现。
前言
对于JavaScript这门语言,其实我更喜欢称它为ECMAScript,从一开始我们就已经涉及到异步编程,但是多数JavaScript开发者从来没有认真思考过自己程序中的异步,到底是怎么实现的,以及为什么会出现。但是由于开发者对JavaScript的需求和项目的复杂程度日渐扩大,特别是对异步的管理越来越令人痛苦,这一切导致我们迫切需要更加强大、更加合理的异步方法,帮我们管理异步的状态。
开始在JavaScript异步编程历史上,我认为一共出现了三种异步编程方式
回调
Promise
Async/Await
由于我不想回忆起很久以前,被回调地狱支配的恐惧,就跳过回调这一块,读者们自行了解(篇幅有限)
(PS:好吧,new Date() --> Sat May 19 2018 23:55:17 GMT+0800 (中国标准时间),写完洗洗睡吧)
Promise是一种范式。在这里扯一句:回调是将我们封装的回调函数交给第三方(甚至可能是外部代码),紧接着我们期待它能够调用我们封装的回调函数。那么Promise就是不把自己的程序传递给第三方,而是第三方给我们提供此任务何时结束,然后由我们自己决定下一步做什么。
设想一个场景,今天早上,我去买早餐,到了一个快餐店,点了一个汉堡(做了一次请求),我就在收银台上支付了这个汉堡的价格(类似于参数的传递),但是我并没有立即得到这个汉堡,而是拿到了一个订单号,这个时候我就只需要坐着等待小姐姐叫到我的订单号(这个时候已经产生了一个Promise),在这个中间等待的时间,我们可以做一些其他的事情,比如打电话、玩儿手机、谈工作等等......(这就是合理利用中间的空隙时间,在js的异步中也可以完全体现,但不在本文的探讨范围内),直到小姐姐叫到XX号码的好了(一个Promise执行结束),结果一般有两种,要么是汉堡做好了(Promise中的resolve()中设置的值),要么是汉堡卖完了(Promise中的reject()中设置的值),这时我就需要考虑换其他食物了。假设汉堡做好了,我再过去拿我的汉堡,拿到过后,我可以自行选择吃掉它或者是扔掉它(当然这是不可能的)(这就体现了具体怎么实现的决定权在我们)如何创建并使用一个Promise
这个例子我觉得还是很形象的,哈哈哈。
首先我先告诉你Promise的决议结果:resovle是完成、reject是拒绝
// -----------------------> Promise 的创建与使用 function creat_promise () { return new Promise((resolve, reject) => { resolve(42) }) } function use_promise () { const a_promise = creat_promise() a_promise.then(res => console.log(res)) } use_promise()Promise的高级使用方法
当你看到这一节时,需要用到自定义的模拟数据请求类
该api类在这儿
或者查看本项目的源码,配合着浏览本文章
// api.js // 定义初始数据 const users = [ { id: 1, name: "jack", year: 12, grade: 1 }, { id: 2, name: "john", year: 12, grade: 1 }, { id: 3, name: "winer", year: 12, grade: 2 } ] // user的father,根据child链接 const fathers = [ {id: 11, child: "jack", name: "jack_father"}, {id: 22, child: "john", name: "john_father"}, {id: 33, child: "winer", name: "winer_father"} ] class Api { // 根据id获取一个User对象的Promise getUserById (id, request_time = 1000, fn = function () {}) { return new Promise((resolve, reject) => { setTimeout(() => { const user = users.find(item => { return item.id === id }) fn() resolve(user) }, request_time) }) } // 根据grade获取一个User对象列表的Promise getUsersByGrade (grade) { return new Promise((resolve, reject) => { setTimeout(() => { const _users = users.filter(item => { return item.grade === grade }) resolve(_users) }, 1000) }) } // 根据user获取一个UserName的Promise getUserName (user, request_time = 1000) { return new Promise((resolve, reject) => { setTimeout(() => { const child = users.find(item => { return item.name === user.name }) resolve(child.name) }, request_time) }) } // 根据userName获取一个Father的Promise getFatherByChildName (childName) { return new Promise((resolve, reject) => { setTimeout(() => { const father = fathers.find(item => { return item.child === childName }) resolve(father) }, 1000) }) } // 抛出一个异常的Promise throw_Error () { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error("api.js-------->抛出了一个错误")) }, 1000) }) } }
首先 const api = new Api()
Promise 的链式顺序模式
// -----------------------> Promise 的链式顺序模式 function promise_chain () { api.getUserById(1) .then(res => { console.log(res) return api.getUserName(res) }) .then(res => { console.log(res) return api.getFatherByChildName(res) }) .then(res => console.log(res)) } promise_chain()
每个Promise.then(..)中的返回值(即使返回的不是Promise对象,因为Promise内在机制会将其转换为一个可以使用的Promise)将会作为下一个Promise对象,即Promise.then( return ...)得到的是一个Promise对象,因此可以不断地Promise.then( return ... ).then( return ... ).then( return ... ).then( return ... ).then( return ... ).then()......
Promise 的catch
function promise_lastErro () { api.throw_Error() .then(res => console.log("没有错")) .catch(err => { console.log(err) foo.bar() }) console.log("无法捕捉最后一个catch的错误:foo.bar()") } promise_lastErro()
Promise 的并发/并行 : Promise.all([...])
传入的参数是一个Promise的数组
如果传入的是[],Promise.all([...])将立即决议为完成
全部Promise完成,Promise.all([...])完成
如果有一个被拒绝,则Promise.all([...])被拒绝
function promise_concurrent_1 () { const p1 = api.getUserById(1,Math.random() * 1000, () => { console.log("p1执行完毕") }) const p2 = api.getUserById(1,Math.random() * 1000, () => { console.log("p2执行完毕") }) Promise.all([p1, p2]) .then(res => console.log("p1 p2 都执行完毕")) } promise_concurrent_1()
Promise 的任务竞争:竞态(第一优先) : Promise.race([...])
传入的参数是一个Promise的数组
如果传入的是[],Promise.race([...])将不会决议,始终处于挂起状态
只要有一个Primise完成,Promise.race([...])则完成
如果有一个被拒绝,则Promise.race([...])被拒绝
function promise_compete () { const p1 = api.getUserById(1, 2000) const p2 = api.getUserById(2) Promise.race([p1, p2]).then(res => console.log(res)) } promise_compete()
关于Promise.all([...])与Promise.race([...])的变体有很多
Promise.none([...]): 要求[...]全部被拒绝,Promise.none([...])决议为完成
Promise.any([...]): 与Promise.all([...])类似,但只要求完成一个即可,但是会执行所有Promise
Promise.first([...]):与Promise.any([...])类似,但是只要有第一个Promise决议为完成,就不关心后面的Promise了
Promise.last([...]):与Promise.first([...])相反,最后一个完成的胜出
到此Promise的基本用法就是这些了吧,不知不觉半个小时过去了。
Async/Await 什么是Async/Await我认为Async/Await是区别于Promise更优雅的体现,它可以简化Promise的大部分代码,让你的代码看上去优雅美观并且大气。并且我支持你,在现在甚至以后,对异步的管理尽可能使用Async/Await。
Async/Await的使用
使用Async/Await代替链式promise(类比 ---> Promise的链式顺序模式)
async function async_Request () { console.log("请稍等..此时是三个setTimeOut,每个1s,需要等待3s") const user = await api.getUserById(1) const userName = await api.getUserName(user) const father = await api.getFatherByChildName(userName) console.log(father) } async_Request()
使用Async/Await的并发与并行
async function async_Concurrent () { const users = await api.getUsersByGrade(1) const usersPromise = users.map(item => api.getUserName(item, Math.random() * 1000)) Promise.all(usersPromise).then(res => { console.log(res) }) } async_Concurrent()
使用Async/Await的错误捕捉
async function async_CatchErro () { try { await api.throw_Error() console.log("未捕捉到错误?") } catch (error) { console.log(error) } } async_CatchErro()
Async/Await函数的互相调用
async function async_A () { const user = await api.getUserById(2) const userName = await api.getUserName(user) const father = await api.getFatherByChildName(userName) return { user, userName, father } } async function async_B () { console.log("数据获取中...") const { user, userName, father } = await async_A() console.log("userInfo",{ user, userName, father }) } async_B()
Async/Await检索十条数据,串行
async function async_ten_serial (length = 10) { try { const users = [] console.log("串行请求10条数据,每条1秒,请稍等10秒钟....") while(users.length < 10) { users.push(await api.getUserById(1)) } console.log(users) } catch (error) { console.log(error) } } async_ten_serial()
Async/Await检索十条数据,并行
async function async_ten_parallel (length = 10) { try { const usersPromise = [] console.log("并行请求10条数据,每条1秒,请稍等1秒钟....") while(usersPromise.length < 10) { usersPromise.push(api.getUserById(2)) } const users = await Promise.all(usersPromise) console.log(users) } catch (error) { console.log(error) } } async_ten_parallel()
ok!本文并没有讲那些概念性的东西,只是简单地讲这几种实现用代码描述出来,更详细的。请大家参考官方文档,其实对于这篇文章的排版,我发现应该将Promise 与 Async/Await对比起来描述,懒得重新排版了,委屈各位手动对比了。(:
源码地址
(PS: newDate() ---> Sun May 20 2018 00:42:15 GMT+0800 (中国标准时间))
不知不觉竟然写了接近一个小时,溜了溜了,不修仙。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95122.html
摘要:接下来介绍下异步编程六种方法。六生成器函数是提供的一种异步编程解决方案,语法行为与传统函数完全不同,最大的特点就是可以控制函数的执行。参考文章前端面试之道异步编程的种方法你不知道的中卷函数的含义和用法替代的个理由 前言 我们知道Javascript语言的执行环境是单线程。也就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。 这种模式虽然实现起...
摘要:等待的基本语法该关键字的的意思就是让编译器等待并返回结果。这里并不会占用资源,因为引擎可以同时执行其他任务其他脚本或处理事件。接下来,我们写一个火箭发射场景的小例子不是真的发射火箭 本文由云+社区发表 本篇文章,小编将和大家一起学习异步编程的未来——async/await,它会打破你对上篇文章Promise的认知,竟然异步代码还能这么写! 但是别太得意,你需要深入理解Promise后,...
摘要:由于是单线程的,这些方法就会按顺序被排列在一个单独的地方,这个地方就是所谓执行栈。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务。 Event Loop 是 JavaScript 异步编程的核心思想,也是前端进阶必须跨越的一关。同时,它又是面试的必考点,特别是在 Promise 出现之后,各种各样的面试题层出不穷,花样百出。这篇文章从现实生活中的例子入手,让你彻底理解 E...
摘要:事件循环从回调队列中获取并将其推送到调用堆栈。如何工作请注意,不会自动将您的回调函数放到事件循环队列中。它设置了一个计时器,当计时器到期时,环境将您的回调函数放入事件循环中,以便将来的某个事件会将其选中并执行它。 我们将通过回顾第一篇文章中单线程编程的缺点,然后在讨论如何克服它们来构建令人惊叹的JavaScript UI。在文章结尾处,我们将分享5个关于如何使用async / awai...
摘要:大家都一直在尝试使用更好的方案来解决这些问题。这是一个用同步的思维来解决异步问题的方案。当我们发出了请求,并不会等待响应结果,而是会继续执行后面的代码,响应结果的处理在之后的事件循环中解决。我们可以用一个两人问答的场景来比喻异步与同步。 在实际开发中总会遇到许多异步的问题,最常见的场景便是接口请求之后一定要等一段时间才能得到结果,如果遇到多个接口前后依赖,那么问题就变得复杂。大家都一直...
阅读 693·2021-11-22 13:52
阅读 1498·2021-09-27 13:36
阅读 2800·2021-09-24 09:47
阅读 2130·2021-09-22 15:48
阅读 3575·2021-09-22 15:39
阅读 1437·2019-08-30 12:43
阅读 2905·2019-08-29 18:39
阅读 3157·2019-08-29 12:51