js_基于期约(promise)的异步串行化调用/js实现钟表clock功能/回调函数和高阶函数/js异步编程:从0开始认识promise/并发模型与事件循环与事件队列
文章目录
早期做法
回调函数
在计算机程序设计中,回调函数,或简称回调(Callback 即call then back 被主函数调用运算后会返回主函数
),是指通过参数将函数传递到其它代码的,某一块可执行代码的引用
(或封装该代码的对象本身)。这一设计允许了底层代码调用在高层定义的子程序。
对于以回调函数对象会参数的高阶函数,只需要将回调函数对象参数角色当作普通的参数来理解,不过该中参数具有一定的功能,他的功能和函数一样.
- 回调函数可以简化代码,让代码更紧凑.
- 减少一些中间变量的创建(中间值的保存和判断)
- 但是一般来说,高阶函数和回调函数可以拆分为用普通函数来实现
例如下面的回调用法,借助回调,我们可以避免一些判断逻辑的同时,也能够实现串行化任务.
同时,代码的复用程度也不错.
function delayedExecute(str, callback = null) { setTimeout(() => { console.log(str); callback && callback(); }, 1000) } delayedExecute('p1 callback', () => { delayedExecute('p2 callback', () => { delayedExecute('p3 callback', () => { delayedExecute('p4 callback'); }); }); });
运行结果 // p1 callback(1 秒后) // p2 callback(2 秒后) // p3 callback(3 秒后) // p4 callback(4 秒后)
另一段探究代码可以表明问题
/* */ function go() { return new Promise( (resolve, reject) => { // ()=>console.log(Date.now()) console.log("before run the setTimeout..."); console.log(Date()) // 期约解决之后(或者状态敲定之后,后续的than所添加的执行逻辑(回调)方可进入异步消息队列) setTimeout(resolve, 1000) console.log("after the setTimeout task be send to the async task queue."); console.log(Date()); console.log("\n"); /* default return resolved */ // resolve() } ) } function test_go() { go() .then(go) .then(go) } // 后续的then()中添加的操作会等待前面的操作期约返回结果 /* 等价于 */ /* go() .then(()=>go()) .then(()=>go()) */ test_go()
运行结果表明,js中,setTimeout()将异步任务进行排队的事件几乎瞬间完成,(体现在该语句前后两个打印事件的语句(几乎是同时发生的)(当然,您可以通过将setTimeout的毫秒参数调大或者将事件打印改为事件戳,可以更能说明问题.
但是这种写法还是有缺点,回调函数对象嵌套的太深了.
实现电子钟表(秒表)功能
/* implement of simple clock: */ function goo() { // return p = new Promise( (resolve) => { // ()=>console.log(Date.now()) console.log(Date()) setTimeout(resolve, 1000) } ) p.then(goo) // return p } goo()
您可以修改setTimeout()的毫秒参数来提高计时精度.(越低越精确)
改进做法
早期没有引入Promise的时候,想要串行化异步调用往往会陷入回调地狱的问题
promise
链式调用方案:
function delayedResolve(str) { return new Promise((resolve, reject) => { console.log(str); setTimeout(resolve, 1000); }); } delayedResolve('p1 executor') .then(() => delayedResolve('p2 executor')) .then(() => delayedResolve('p3 executor')) .then(() => delayedResolve('p4 executor'))
结果
// p1 executor(0 秒后) // p2 executor(1 秒后) // p3 executor(2 秒后) // p4 executor(3 秒后)
使用高阶函数风格的好处
嗯,我们先将这个被作为参数的函数(或函数对象的引用)记为pf(parameter_functoin); 将以pf为参数的高阶函数记为hf(higherOrder_fucntion)
笼统的讲:
回调函数:传递给高阶函数的函数
高阶函数:接收函数(回调函数)为参数或返回值为函数的函数
一个典型的例子是:
//定义三个函数(将作为回调函数传给高阶函数calculator) function add(num1, num2) { return num1 + num2; } function sub(num1, num2) { return num1 - num2; } function multiply(num1, num2) { return num1 * num2; } /* origin version calculator: */ // function calculator(num1, num2, operator) { // return operator(num1, num2); // } /* the more robust calculator*/ function calculator(num1, num2, operator) { // Check inputs are number or not - Common logic if (isNaN(num1) || isNaN(num2)) { console.log("\terror:input(s) are not a number:"); return; } return operator(num1, num2); } ret1 = calculator(10, 5, add); console.log("result1:" + ret1) ret2 = calculator(10, 5, sub); console.log("result2:" + ret2) ret3 = calculator(10, "NotNumberArgument", multiply); console.log( "result3:" + ret3)
可以看到,通过高阶函数接收回调函数的形式,我们为一类函数的执行做了统一的类型判断
而且,operator 还是软编码
从0开始理解promise
link0
优雅的异步处理 - 学习 Web 开发 | MDN (mozilla.org)
这是位于js教程中的一篇内容
Promise术语回顾
在上面的部分中有很多要介绍的内容,所以让我们快速回过头来给你一个简短的指南,你可以将它添加到书签中,以便将来更新你的记忆。你还应该再次阅读上述部分,以确保这些概念坚持下去。
- 创建promise时,它既不是成功也不是失败状态。这个状态叫作 pending (待定)。
- 当promise返回时,称为 resolved (已解决).
- 一个成功resolved的promise称为 fullfilled ( 实现 )。它返回一个值,可以通过将
.then()
块链接到promise链的末尾
来访问该值
。<span> </span>.then()
块中的执行程序函数将包含promise的返回值。 - 一个不成功resolved的promise被称为 rejected ( 拒绝 )了。它返回一个原因( reason ),一条错误消息,说明为什么拒绝promise。可以通过将
.catch()
块链接到promise链的末尾来访问此原因。
- 一个成功resolved的promise称为 fullfilled ( 实现 )。它返回一个值,可以通过将
link1
MDN reference link:该页面介绍了promise最常用的部分
一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。* Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。
- 如同字面意思一样,它不可能还只是个壳子(期约),但是我们的后续操作(依赖于异步操作的结果的后续操作)可以建立在这个可能尚未被确定值的期约上
- 正因为我们使用期约的时候往往是我们不知道期约所代表的结果(异步操作的执行结果的成败),所以我们往往要准备两套方案(两个回回调函数)来分别处理promise的两种不同的稳态结果.
- 更直白的,我们可以将promise理解为一种对结果的设想,并将这种设想具象化(实例化),然后我们对这个设想执行一些handler的绑定;待到promise状态确定下来后,就可以按情况执行其中一种的后续处理方案
- 早期没有promise(异步执行的结果没有结果替身,就只好嵌套着递归来实现后续处理
- 到了绑定部分,handler(resolved/rejected)函数对象本身要作为参数传递给then();同时,handler本也是函数的角色,所以往往也接受参数,这个参数正是期约(promise)状态稳定下来后所带出的结果(异步逻辑成功执行的结果或者执行失败的异常结果res[ult])
let myFirstPromise = new Promise(function(resolve, reject){ //当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...) //在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法. setTimeout(function(){ resolve("成功!"); //代码正常执行! }, 2000); }); //在这里为myFirstPromise绑定resolve回调的具体逻辑(通过then()行数执行相应的绑定 myFirstPromise.then(function(successMessage){ //successMessage的值是上面调用resolve(...)方法传入的值. //successMessage参数不一定非要是字符串类型,这里只是举个例子 console.log("Yay! " + successMessage); });
链式调用
创建Promise
const wait = (ms) => { new Promise( (resolve) => {setTimeout(resolve, ms)} ) }; //wait:指向函数对象 //ms为毫秒数(参数) //resolve 作为handler(回调函数)传递给setTimeout() //函数对象(resolve) => {setTimeout(resolve, ms)}作为Promise构造函数的执行器参数 /*调用wait*/ //wait(1000)将创建一个Promise(以函数执行器对象(resolve) => {setTimeout(resolve, ms)})为参数; 执行器在1000ms(也可能更久)后将会执行resolve所指的函数 由通过then传递具体的处理逻辑给resolve形参); wait(10000).then( () => saySomething("10 seconds") ).catch(failureCallback);
通常,Promise 的构造器接收一个执行函数(executor),我们可以在这个执行函数里手动地 resolve 和 reject 一个 Promise。
既然 setTimeout 并不会真的执行失败,那么我们可以在这种情况下忽略 reject。
promise 经验法则
一个好的经验法则是总是返回或终止 Promise 链,并且一旦你得到一个新的 Promise,返回它
。
doSomething() .then(function(result) { return doSomethingElse(result); }) .then(newResult => doThirdThing(newResult)) .then(() => doFourthThing()) .catch(error => console.log(error));
link2
过早地处理被拒绝的 promise 会对之后 promise 的链式调用造成影响
执行顺序
link3
像promise这样的异步操作被放入事件队列中,事件队列在主线程完成处理后运行,这样它们就不会阻止后续JavaScript代码的运行。排队操作将尽快完成
(仍然由主线程负责排队),然后将结果返回到JavaScript环境
。
可视化描述(运行时概念)
stack中存放函数栈帧
一个函数调用就会向栈中压入一个帧(尤其时嵌套调用,递归调用)
消息队列和函数栈
在 事件循环 期间的某个时刻,运行时会从最先进入队列的消息开始处理队列中的消息。
被处理的消息
会被移出队列,并作为输入*参数*
来调用与之关联的系列函数
。正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧。
函数的处理会一直进行到执行栈再次为空为止;
然后事件循环将会处理队列中的下一个消息(如果还有的话)。
添加消息到队列
在浏览器里,每当一个事件发生并且有一个事件监听器绑定在该事件上时,一个消息就会被添加进消息队列。如果没有事件监听器,这个事件将会丢失。所以当一个带有点击事件处理器的元素被点击时,就会像其他事件一样产生一个类似的消息。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2022-01-05 css_选择器进阶/如何简单提高选择器的优先级(权重)/类选择器及其衍生
2022-01-05 vscode/typora+picGo-core(命令行CLI)/picGo(GUI)+图片上传(github/smms)/批量上传/typora语法扩展渲染功能设置/修改本地图片存放位置配置