JS-29-Promise对象

news/2024/10/21 7:38:51/

一、JavaScript的异步操作

在JavaScript的世界中,所有代码都是单线程执行的。

由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现:

javascript">function callback() {console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒钟后调用callback函数
console.log('after setTimeout()');

观察上述代码执行,在Chrome的控制台输出可以看到:

before setTimeout()
after setTimeout()
(等待1秒后)
Done

可见,异步操作会在将来的某个时间点触发一个函数调用

AJAX就是典型的异步操作。以上一节的代码为例:

javascript">request.onreadystatechange = function () {if (request.readyState === 4) {if (request.status === 200) {return success(request.responseText);} else {return fail(request.status);}}
}

把回调函数success(request.responseText)fail(request.status)写到一个AJAX操作里很正常,但是不好看,而且不利于代码复用。

有没有更好的写法?比如写成这样:

javascript">var ajax = ajaxGet('http://...');
ajax.ifSuccess(success).ifFail(fail);

这种链式写法的好处在于,先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success函数或fail函数。

古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。

1-1、setTimeout函数

setTimeout 是 JavaScript 中一个非常常用的函数,用于在指定的延迟后执行一个函数或计算一个表达式。它返回一个代表定时器的ID,这个ID可以用来在将来必要的时候取消定时器(使用 clearTimeout 函数)。

语法:

javascript">var timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
  • func|code:要执行的函数或要计算的表达式。
  • delay:可选参数,表示延迟的毫秒数,即多长时间后执行函数或表达式。默认是 0。
  • arg1, arg2, ...:可选参数,表示传递给函数的额外参数。

示例:

javascript">setTimeout(function() {  console.log('Hello, world!');  
}, 2000);

在这个例子中,console.log('Hello, world!') 将在 2 秒(2000 毫秒)后执行。

javascript">function greet(name) {  console.log('Hello, ' + name + '!');  
}  setTimeout(greet, 2000, 'Alice');

在这个例子中,greet 函数将在 2 秒后执行,并且会传递 'Alice' 作为参数。

setTimeout 只会执行一次指定的函数或代码。如果你想要重复执行某个函数或代码,你应该使用 setInterval 或者在函数内部再次调用 setTimeout

1-2、清除定时器 clearTimeout 函数

如果你想要在某个时刻取消定时器,你可以使用 clearTimeout 函数,并传入 setTimeout 返回的定时器ID。

语法:

javascript">clearTimeout(timeoutID)

示例1: 

javascript">var timerId = setTimeout(function() {  console.log('This will not be logged.');  
}, 5000);  // 假设在某个时刻,我们决定取消这个定时器  
clearTimeout(timerId);

在这个例子中,由于我们调用了 clearTimeout,所以 console.log 不会被执行。

示例2:

javascript">// 设置一个定时器,将在 2 秒后执行  
var timerId = setTimeout(function() {  console.log('Timer executed!');  
}, 2000);  // 在 1 秒后取消定时器  
setTimeout(function() {  clearTimeout(timerId);  console.log('Timer cleared!');  
}, 1000);

你将在控制台看到 "Timer cleared!" 的输出,而不会看到 "Timer executed!" 的输出。

1-3、链式调用

链式调用,允许我们在单个对象上连续调用多个方法,并且每次方法调用都返回同一个对象,以便可以进一步调用其他方法。

优点:这种模式可以提高代码的可读性和简洁性。

 1-4、Promise对象

Promise有各种开源实现,在ES6中被统一规范,由浏览器直接支持。

在JavaScript中,Promise对象用于处理异步操作,它代表了一个可能现在、将来或永远不可用的值。

Promise对象有三种状态pending(进行中)、fulfilled(已成功)和rejected(已失败)。一旦Promise的状态从pending变为fulfilledrejected,就不会再改变。

创建Promise对象的基本语法如下:

javascript">const promise = new Promise((resolve, reject) => {  // 异步操作代码  if (/* 异步操作成功 */) {  resolve(value); // 将Promise的状态设置为fulfilled,并传递一个值  } else {  reject(error); // 将Promise的状态设置为rejected,并传递一个错误  }  
});

 resolvereject都是函数,它们由Promise构造函数传递进来,用于处理异步操作的结果。

示例:

javascript">const promise = new Promise((resolve, reject) => {  setTimeout(() => {  const success = true;  if (success) {  resolve('异步操作成功!');  } else {  reject('异步操作失败!');  }  }, 1000);  
});  promise.then(value => {  console.log(value); // 如果Promise状态为fulfilled,则执行这里的代码  
}).catch(error => {  console.error(error); // 如果Promise状态为rejected,则执行这里的代码  
});

在JavaScript的Promise构造函数中,resolvereject是两个非常关键的函数参数。它们被用来改变Promise对象的状态,并传递最终的结果或错误给Promise链中的后续处理函数。

1、resolve函数

resolve函数用于将Promise对象的状态从pending(进行中)变为fulfilled(已完成),并传递一个值给后续的.then()处理函数

2、reject函数

reject函数用于将Promise对象的状态从pending变为rejected(已拒绝),并传递一个错误对象给后续的.catch()处理函数

3、Promise链中的错误处理

Promise链中,如果你没有在每个.then()之后立即使用.catch()来处理可能发生的错误。

你可以在链的末尾使用单个.catch()来捕获所有之前的.then()中可能抛出的错误。这是因为,一旦Promise链中的某个环节发生错误,该错误会“冒泡”到链的末尾,除非在某个环节被捕获处理。

javascript">promise  .then(result => {  // 处理结果...  // 如果这里发生错误且没有捕获,它会传递到链的末尾的.catch()中  })  .then(anotherResult => {  // 处理另一个结果...  // 同样,这里的错误也会“冒泡”到链的末尾  })  .catch(error => {  // 处理链中任何环节的错误  console.error(error);  });

我们先看一个最简单的Promise例子:

生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:

javascript">    <div id="test-promise-log" style="border: solid 1px #ccc; padding: 1em; margin: 15px 0;"><p>Log:</p></div><script>"use script"function testLian(){// 清除log:var logging = document.getElementById('test-promise-log');while (logging.children.length > 1) {logging.removeChild(logging.children[logging.children.length - 1]);}// 输出log到页面:function log(s) {var p = document.createElement('p');p.innerHTML = s;logging.appendChild(p);}new Promise(function (resolve, reject) {log('start new Promise...');var timeOut = Math.random() * 2;log('set timeout to: ' + timeOut + ' seconds.');setTimeout(function () {if (timeOut < 1) {log('call resolve()...');resolve('200 OK');}else {log('call reject()...');reject('timeout in ' + timeOut + ' seconds.');}}, timeOut * 1000);}).then(function (r) {log('Done: ' + r);}).catch(function (reason) {log('Failed: ' + reason);});}</script>

可见Promise最大的好处是在异步执行的流程中,把执行代码处理结果的代码清晰地分离了:

4、Promise处理若干异步任务

Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。

串行执行这样的异步任务,不用Promise需要写一层一层的嵌套代码。有了Promise,我们只需要简单地写:

javascript">job1.then(job2).then(job3).catch(handleError);

其中,job1job2job3都是Promise对象。

下面的例子演示了如何串行执行一系列需要异步计算获得结果的任务:

javascript">// 0.5秒后返回input*input的计算结果:
function multiply(input) {return new Promise(function (resolve, reject) {log('calculating ' + input + ' x ' + input + '...');setTimeout(resolve, 500, input * input);});
}// 0.5秒后返回input+input的计算结果:
function add(input) {return new Promise(function (resolve, reject) {log('calculating ' + input + ' + ' + input + '...');setTimeout(resolve, 500, input + input);});
}var p = new Promise(function (resolve, reject) {log('start new Promise...');resolve(123);
});p.then(multiply).then(add).then(multiply).then(add).then(function (result) {log('Got value: ' + result);
});

setTimeout可以看成一个模拟网络等异步执行的函数。

1-5、AJAX异步执行函数转换为Promise对象

现在,我们把上一节的AJAX异步执行函数转换为Promise对象,看看用Promise如何简化异步处理:

javascript">'use strict';// ajax函数将返回Promise对象:
function ajax(method, url, data) {var request = new XMLHttpRequest();return new Promise(function (resolve, reject) {request.onreadystatechange = function () {if (request.readyState === 4) {if (request.status === 200) {resolve(request.responseText);} else {reject(request.status);}}};request.open(method, url);request.send(data);});
}var log = document.getElementById('test-promise-ajax-result');
var p = ajax('GET', '/api/categories');
p.then(function (text) { // 如果AJAX成功,获得响应内容log.innerText = text;
}).catch(function (status) { // 如果AJAX失败,获得响应代码log.innerText = 'ERROR: ' + status;
});

1-6、Promise.all()

除了串行执行若干异步任务外,Promise还可以并行执行异步任务。

试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:

javascript">        "use script"var p1 = new Promise(function (resolve, reject) {setTimeout(resolve, 500, 'A1');});var p2 = new Promise(function (resolve, reject) {setTimeout(resolve, 600, 'A2');});// 同时执行p1和p2,并在它们都完成后执行then:Promise.all([p1, p2]).then(function (results) {// 获得一个Array: ['A1', 'A2']console.log(results);});

1-7、Promise.race()

有些时候,多个异步任务是为了容错

比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现:

javascript">var p1 = new Promise(function (resolve, reject) {setTimeout(resolve, 500, 'A1');
});
var p2 = new Promise(function (resolve, reject) {setTimeout(resolve, 600, 'A2');
});
Promise.race([p1, p2]).then(function (result) {console.log(result); // 'A1'
});

由于p1执行较快,Promise的then()将获得结果'A1'p2仍在继续执行,但执行结果将被丢弃

如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。


http://www.ppmy.cn/news/1427688.html

相关文章

4.18学习总结

多线程补充 等待唤醒机制 现在有两条线程在运行&#xff0c;其中一条线程可以创造一个特殊的数据供另一条线程使用&#xff0c;但这个数据的创建也有要求&#xff1a;在同一时间只允许有一个这样的特殊数据&#xff0c;那么我们要怎样去完成呢&#xff1f;如果用普通的多线程…

友思特应用 | 红外视角的延伸:短波红外相机的机器视觉应用

导读 短波红外SWIR在不同波段针对不同材料的独特成像特征为各领域检测应用的拓宽提供了基础。本文将展现短波红外成像技术在水分检测、塑料检测、太阳能电池板检查和矿场开采等领域的丰富应用案例&#xff0c;讨论短波红外相机在未来的发展方向。 SWIR 背景简介 短波红外 &am…

实战:通用二进制格式安装 MySQL(mysql-5.7.29)-2024.4.6(测试成功)

目录 文章目录 目录实验环境下载url安装相关包准备用户准备二进制程序准备环境变量准备配置文件生成数据库文件,并提取root密码准备服务脚本和启动修改口令测试登录安全初始化&#xff08;可选&#xff09;shell一键安装关于我最后 实验环境 mysql-5.7.29 centos7.6 1810软件位…

高频前端面试题汇总之Vue篇

1. Vue的基本原理 当一个Vue实例创建时&#xff0c;Vue会遍历data中的属性&#xff0c;用 Object.defineProperty&#xff08;vue3.0使用proxy &#xff09;将它们转为 getter/setter&#xff0c;并且在内部追踪相关依赖&#xff0c;在属性被访问和修改时通知变化。 每个组件实…

Linux 安装 Docker +Docker Compose + cucker/get_command_4_run_container

TIP&#xff1a;下面演示的 Linux 系统为 CentOS 7.9。 Docker 更新你的系统并安装必要的依赖项&#xff1a; sudo yum update -y sudo yum install -y yum-utils device-mapper-persistent-data lvm2添加 Docker 的官方仓库&#xff1a; sudo yum-config-manager --add-rep…

es安装中文分词器

下载地址&#xff0c;尽量选择和自己本地es差不多的版本 https://github.com/infinilabs/analysis-ik/releases 下载好&#xff0c;解压&#xff0c;把里面的文件放到es的plugins/ik目录下 把plugin-descriptor.properties文件里的es版本改成自己对应的 再启动es&#xff0c;能…

FlinkSQL State的生命周期

FlinkSQL未显示配置state生命周期 FlinkSQL默认没有配置state 的过期时间。也就是说默认情况是FlinkSQL从不清除状态。如果状态后端保存在rocksdb中&#xff0c;直到本地磁盘被打满&#xff0c;服务挂掉&#xff0c;报错如下&#xff1a; java.io.IOException: [bf3ba881614e…

【linux】centos7 开机 进单用户模式修改root密码

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…