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术语回顾

在上面的部分中有很多要介绍的内容,所以让我们快速回过头来给你一个简短的指南,你可以将它添加到书签中,以便将来更新你的记忆。你还应该再次阅读上述部分,以确保这些概念坚持下去。

  1. 创建promise时,它既不是成功也不是失败状态。这个状态叫作 pending (待定)。
  2. 当promise返回时,称为 resolved (已解决).
    1. 一个成功resolved的promise称为 fullfilled实现 )。它返回一个值,可以通过将 .then()块链接到promise链的末尾访问该值<span> </span>.then()块中的执行程序函数将包含promise的返回值。
    2. 一个不成功resolved的promise被称为 rejected拒绝 )了。它返回一个原因( reason ),一条错误消息,说明为什么拒绝promise。可以通过将 .catch()块链接到promise链的末尾来访问此原因。

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 more detail

过早地处理被拒绝的 promise 会对之后 promise 的链式调用造成影响

执行顺序

在这里插入图片描述

link3

并发模型与事件循环

事件队列

像promise这样的异步操作被放入事件队列中,事件队列在主线程完成处理后运行,这样它们就不会阻止后续JavaScript代码的运行。排队操作将尽快完成(仍然由主线程负责排队),然后将结果返回到JavaScript环境

可视化描述(运行时概念)
在这里插入图片描述
stack中存放函数栈帧
一个函数调用就会向栈中压入一个帧(尤其时嵌套调用,递归调用)

消息队列和函数栈

在 事件循环 期间的某个时刻,运行时会从最先进入队列的消息开始处理队列中的消息。

被处理的消息会被移出队列,并作为输入*参数*来调用与之关联的系列函数。正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧。

函数的处理会一直进行到执行栈再次为空为止;
然后事件循环将会处理队列中的下一个消息(如果还有的话)。

添加消息到队列

添加消息

在浏览器里,每当一个事件发生并且有一个事件监听器绑定在该事件上时,一个消息就会被添加进消息队列。如果没有事件监听器,这个事件将会丢失。所以当一个带有点击事件处理器的元素被点击时,就会像其他事件一样产生一个类似的消息。

async/await

link4

async/await

posted @   xuchaoxin1375  阅读(6)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享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语法扩展渲染功能设置/修改本地图片存放位置配置
点击右上角即可分享
微信分享提示