目录
- 前言
- 回顾
- Promise
- Promise.all
- async
- 需求
- 实现
- 准备准备
- 数据
- 封装请求
- 原生
- Promise
- async
- 文末
前言
2022年全国甲卷高考语文作文已然出炉,笔者看到后居然想起了这个表情包
好家伙,还好我提前成为了一名默默在卷大学生,不然以这篇作文的难度足以让我招架不住。话不多说,咱们来看看百度作文给出的题目解析:
这是一则材料作文。材料由两个部分组成,第一部分是现象,介绍传统文化经典《红楼梦》中众人给匾额题名三种不同的方式;第二部分是对上述三种题名方法的评价,暗示了对传统文化的继承、发展与创新。“直接移用”,“借鉴化用”,“根据情境独创”,也就是直接学习、借鉴、创新三步走。在此基础上,由文学作品中的情节内涵拓展到更广泛的领域,引发学生深入思考。
透过这个解析我们可以看到这则材料作文由两个部分构成,第一部分是现象,具体是指《红楼梦》中众人给匾额题名三种不同的方式;第二部分则是对上述三种题名方法的评价。由于笔者水平有限,所以决定在直接引用、借鉴化用、根据情境独创
这三个题眼上下手,那么它们所对应的顺序也自然是学习、借鉴、创新
,通过一步一步的摸索,最终达到好的理想状况,而又经过对题目解析的反复揣摩,笔者脑中忽然灵光一现,我能不能以JavaScript中处理并发请求的几种方式为素材来试着写写这篇作文呢?
因笔者水平有限,本篇文章所理解内容仅为个人观点。我会尝试将“翼然”、“泻玉”、“沁芳”分为三个阶段,每个阶段都是对前一个阶段的升华,通过一步一步的摸索、前进,最终达到“沁芳”的理想层面。
我之前的介绍过关于异步任务的题目虽然也不少,但究竟应该如何优雅的处理并发请求呢?要知道在ES6
之前,我们发送依赖请求大概如下所示:
场景:根据用户id(001),获取到用户姓名后,再获取用户性别,再获取用户年龄
function getUserName(id, callback) { setTimeout(function () { callback('001姓名为鲨鱼辣椒') }, 1000) } function getUserSex(id, callback) { setTimeout(function () { callback('001性别为男') }, 1000) } function getUserAge(id, callback) { setTimeout(function () { callback('001年龄为5') }, 1000) } // 开始套娃 getUserName('001', function (name) { console.log(name) getUserSex('001', function (sex) { console.log(sex) getUserAge('001', function (age) { console.log(age) }) }) })
由上述代码可知,这种写法通常会饱受回调地狱的“折磨”,尤其是当依赖的请求变的越来越多时,我们的“屎山”也便一发不可收拾了。当Promise
问世之后,我们来看看又该如何优化,而这一优化是不是也代表着“翼然”向“泻玉”所作出的转变呢?
function getUserName(id) { return new Promise(function (resolve) { setTimeout(function () { resolve('001姓名为鲨鱼辣椒') }, 1000) }) } function getUserSex(id) { return new Promise(function (resolve) { setTimeout(function () { resolve('001性别为男') }, 1000) }) } function getUserAge(id) { return new Promise(function (resolve) { setTimeout(function () { resolve('001年龄为5') }, 1000) }) } // 禁止套娃,一切都开始扁平了起来 getUserName('001').then(function (data) { console.log(data) return getUserSex('001') }).then(function (data) { console.log(data) return getUserAge('001') }).then(function (data) { console.log(data) })
由此可见,当应用了Promise
之后,这座“屎山”也随之被铲平了。如果说then
的链式调用足以磨平请求代码的“屎山”,那么我们今天用一个需求,来展现async
降维打击的能力之强,下面我们开始逐渐处理并发请求,也一道解开“翼然”、“泻玉”、“沁芳”之间的关系
回顾
Promise
Promise
的特点是同步执行代码,且在未决议之前拥有三种状态:初始化状态为pending
、成功状态为resolve
、失败状态为rejected
。在未调用resolve
以及rejected
之前,状态始终保持为pending
,调用resolve
和rejected
后状态分别为成功/失败,要注意的是状态一旦确定,不可更改。每个Promise对象都会返回一个then
方法,这个then
方法会在Promise
对象成功/失败时如期调用,因为这是Promise
所作出的承诺,例如:
var p = new Promise((resolve, rejected) => { // 1秒钟之后调用resolve,将初始化状态改为成功状态 // 注意,在此1秒钟之前,p的状态始终为初始化状态,因为Promise尚未作出决议 setTimeout(resolve, 1000) }) // then方法会在约1秒钟后执行 p.then(data => console.log(data))
Promise.all
all
方法接收一个具有iterator
接口的数据,例如数组。要注意的是这个数组中的每一个成员都必须是一个Promise
对象,all
方法会返回这些Promise
对象成功或失败的值,例如:
var p = async () => await new Promise(r => setTimeout(r, 1000)) var f = Promise.all([p, p]) // then方法会在约1秒钟后执行 p().then(data => console.log(data))
async
async
函数始终会返回一个Promise
对象,这个Promise
对象的状态取决于async
函数的返回值。await
关键字的职责就是异步求值,通常后面会是一个Promise
对象,await
关键字会“自动”帮我们then
这个Promise
对象,然后拿到数据进行返回。注意,只有当await
后面的这个Promise
对象决议了,async
函数才会继续往下执行,否则交出线程转而执行其它任务,例如:
// f会在约1秒钟后决议 var f = async () => await new Promise(r => setTimeout(r, 1000)) // then方法会在约1秒钟后执行 f().then(data => console.log(data))
需求
回顾完成,接下来我们通过这个需求来解开“翼然”、“泻玉”、“沁芳”之间的关系。比如我有一个装有一百个书本id
的数组,我需要获取每个书本的详细内容,虽然是一百本书,但由于只有一个接口,所以需要重复调用,只不过在调用时每次的入参id均不同而已
实现
准备准备
数据
books中存储着每个书本的id,我们需要通过这些枯燥无味的id来拿到每个书本所对应的信息
var books = [ {id: "0000"}, {id: "1111"}, {id: "2222"}, {id: "3333"}, // more... ]
封装请求
我们先封装一个request
方法,用于请求单个书本id所对应的信息。因为没有这么合适的接口来供我们练习,所以此处暂时决定使用setTimeout
来代替请求区域
// request方法会在约1秒钟后“请求成功” var request = id => new Promise(resolve => setTimeout(resolve, 1000))
原生
通过xhr
封装一个请求,要注意的是这里的请求函数requestJs
并未用到Promise
对象,而是使用了原汁原味的回调模式,因为在ES6
之前Promise
尚未面世,这也正对应了最开始的阶段——“翼然”
var info = [] var requestJs = (id, callback) => { var xhr = new XMLHttpRequest() open('GET', 'url') xhr.send(null) xhr.onreadyStatechange = function () { if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status < 300) { callback(xhr.responseText) } } } } // 通过定时器来模拟请求 var requestJsPolyill = (id, callback) => { setTimeout(() => { callback(id) }, 1000) } var judge = data => { info.push(data) if (books.length === info.length) { console.log('请求到的数据:', info) } } books.forEach(v => requestJsPolyill(v.id, judge))
Promise
每请求到一个书本的信息就追加到info
数组中,每次追加后,来判断books
与info
的长度是否相同,如果相同,则代表所有书本信息均已请求完毕,如果不同则代表尚有书本信息未请求成功,我们正是通过这种方式来处理并发请求,可以看到的是Promise
这种方式正是对原生JS的一种化用,或者说是一种更好的改变,所以也就符合了“泻玉”这一阶段
// 存储获取到的书本信息 var info = [] var f = new Promise(resolve => { books.forEach(v => { // 根据books中每个id进行请求 request(v.id).then(() => { // 每请求到一个书本信息都会放在info中 info.push(v.id) // 当请求完的信息数量与原始书本的数量对应起来时,则代表全部请求完成 if (books.length === info.length) { resolve(info) } }) }) }) // 获取完全部书本信息后执行then方法 f.then(data => { console.log('请求到的数据:', data) })
async
由于map
方法会返回一个新数组,所以此处通过map
来对books
中的每一个书本id都加工成一个请求,通过request
来完成这个请求,而request
又会返回一个Promise
对象,这样一来,返回的这个新数组中的每个成员就都是一个Promise
对象,而Promise.all
方法在接收到这个新数组后,会并发执行所有Promise
对象,等到所有Promise
对象都决议后,then
方法才会被调用。使用async
来完成这个需求,无论是代码简洁程度还是可读性,都大大的超越了“翼然”和“泻玉”这两个阶段,也可以说这正是“沁芳”的独特所在
// 当新数组中的所有Promise全部决议后,then会被如期调用 var f = Promise.all(books.map(async v => await request(v.id))) f.then(data => { console.log('请求到的数据:', data) })
文末
我们分别使用了三种方式来处理并发请求,期间我们也阐述了它们各自的特点,而这也正好对应了文章开头所提到的材料作文两个部分:众人题匾的三种方式以及对三种方式的评价。在Promise未面世之前,我们通常使用原生JavaScript
来处理请求,常用的手法也自然是使用回调地狱,这一阶段算是处于初始阶段,也就正好对应了“翼然”这一层面,而通过对原生JavaScript请求方式的改善,将多层嵌套逐渐扁平化来优化代码,通过这一点Promise
也正符合了“泻玉”这一阶段,看到async
的实现方式才明白原来代码还可以如此优雅,比如可读性、扩展性都要比前者强出不少,可能这大概就是创新的魅力所在了吧。无论是“翼然”还是“泻玉”,距离我们理想的代码层面尚有些许不足,而正是这些不足的存在,我们才能一步一步的创新、前进,最终达到“沁芳”的理想层面
到此这篇关于Javascript如何理解全国甲卷高考作文的文章就介绍到这了,更多相关js理解全国甲卷高考作文内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!