Promise.all的深入理解「建议收藏」

Promise.all的深入理解「建议收藏」异步之PromisePromise.allPromise.all接收的promise数组是按顺序执行的还是一起执行的,也就是说返回的结果是顺序固定的吗?目前有两种答案:应该是同步执行的,但是这样就有效率问题了,如果想改成异步执行怎么办呢?有些人认为结果是按顺序执行的,有些人认为结果顺序不确定。那么我们根据实现来解密:环境为:vscode1.20….

大家好,又见面了,我是你们的朋友全栈君。

异步之Promise

Promise.all

Promise.all接收的promise数组是按顺序执行的还是一起执行的,也就是说返回的结果是顺序固定的吗?

目前有两种答案:

  1. 应该是同步执行的,但是这样就有效率问题了,如果想改成异步执行怎么办呢?
  2. 有些人认为结果是按顺序执行的,有些人认为结果顺序不确定。

那么我们根据实现来解密:

环境为:

vscode 1.20.1
node   v8.9.0
npm    v5.6.0

实验代码:

// 获取随机数,toFixed为四舍五入保留小数,0为保留整数,范围~1000
const getRandom = () => +(Math.random()*1000).toFixed(0);

const asyncTask = (taskID) => new Promise( (resolve) => {
    // 随机获取一次0~1000的随机数
    let timeout = getRandom();
    // 打印出传递进来的ID号 taskID=1 start.
    console.log(`taskID=${taskID} start.`);
    // 设置计时时间,function()等价于 () => {...}
    setTimeout(function() {
        // 打印出执行的taskID,和timeout
        console.log(`taskID=${taskID} finished in time=${timeout}.`);
        // 异步成功执行
        resolve(taskID)
    }, timeout);
});

Promise.all([asyncTask(1),asyncTask(2),asyncTask(3)])
.then(resultList => {
    console.log('results:',resultList);
});

实验结果如下:

第一次

taskID=1 start.
taskID=2 start.
taskID=3 start.
taskID=2 finished in time=321.
taskID=3 finished in time=506.
taskID=1 finished in time=932.
results:
Array(3) [1, 2, 3]

第二次

taskID=1 start.
taskID=2 start.
taskID=3 start.
taskID=1 finished in time=243.
taskID=3 finished in time=305.
taskID=2 finished in time=792.
results:
Array(3) [1, 2, 3]

第三次

taskID=1 start.
taskID=2 start.
taskID=3 start.
taskID=3 finished in time=380.
taskID=1 finished in time=539.
taskID=2 finished in time=782.
results:
Array(3) [1, 2, 3]

补充知识介绍

// toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。
NumberObject.toFixed(num)
// num	必需。规定小数的位数,是 0 ~ 20 之间的值,包括 0 和  
// 20,有些实现可以支持更大的数值范围。如果省略了该参数,将用 0 代替。

Promise构造函数只有一个参数,该参数是一个函数,被称作执行器,执行器有2个参数,分别是resolve()和reject(),一个表示成功的回调,一个表示失败的回调。

new Promise(function(resolve, reject) {
  setTimeout(() => resolve(5), 0)
}).then(v => console.log(v)) // 5

记住,Promise实例只能通过resolve或者reject函数来返回,并且使用then()或者catch()获取,不能在new Promise里面直接return,这样是获取不到Promise返回值的。


由此可见,Promise.all 里的任务列表[asyncTask(1),asyncTask(2),asyncTask(3)],我们是按照顺序发起的。
但是根据结果来说,它们是异步的,互相之间并不阻塞,每个任务完成时机是不确定的,尽管如此,所有任务结束之
后,它们的结果仍然是按顺序地映射到resultList里,这样就能和Promise.all里的任务列表
[asyncTask(1),asyncTask(2),asyncTask(3)]一一对应起来。

深入理解Promise.all()

*可能看到这里有些人没有清楚,为什么返回一个数组? *
我们在来看一下这段代码:

Promise.all([asyncTask(1),asyncTask(2),asyncTask(3)])
.then(resultList => {
    console.log('results:',resultList);
});

通常我们在使用异步的时候都是只有一个Promise,现在我们使用all()方法包装多个Promise实例。

语法很简单:参数只有一个,可迭代对象,可以是数组,或者Symbol类型等。

Promise.all(iterable).then().catch()

传入3个Promise实例:

Promise.all([
  new Promise(function(resolve, reject) {
    resolve(1)
  }),
  new Promise(function(resolve, reject) {
    resolve(2)
  }),
  new Promise(function(resolve, reject) {
    resolve(3)
  })
]).then(arr => {
  console.log(arr) // [1, 2, 3]
})

那么我们回头想想应该明白了吧?
因为我们传入的是数组,那么返回的必须是数组,并且会将讲过进行映射。

Promise.race()

语法和all()一样,但是返回值有所不同,race根据传入的多个Promise实例,只要有一个实例resolve或者reject,就只返回该结果,其他实例不再执行。

我们简单看一下例子,返回结果为3,因为我们设置了定时器,第三个Promise执行的最快。

Promise.race([
  new Promise(function(resolve, reject) {
    setTimeout(() => resolve(1), 1000)
  }),
  new Promise(function(resolve, reject) {
    setTimeout(() => resolve(2), 100)
  }),
  new Promise(function(resolve, reject) {
    setTimeout(() => resolve(3), 10)
  })
]).then(value => {
  console.log(value) // 3
})

异步为什么使用箭头函数

这是我一直困惑的原因,我们将前面的例子进行改造一下。

如下:

const getRandom = () => +(Math.random()*1000).toFixed(0);

function test(taskID) {
  return new Promise( (resolve) => {
    // 随机获取一次0~1000的随机数
    let timeout = getRandom();
    // 打印出传递进来的ID号
    console.log(`taskID=${taskID} start.`);
    setTimeout(function() {
        console.log(`taskID=${taskID} finished in time=${timeout}.`);
        resolve(taskID)
    }, timeout);
} )
}

Promise.all([test(1),test(2),test(3)])
.then(resultList => {
    console.log('results:',resultList);
});

我们先来看一下结果是怎样的?

第一次:

taskID=1 start.
taskID=2 start.
taskID=3 start.
results:
Array(3) [undefined, undefined, undefined]
taskID=1 finished in time=460.
taskID=2 finished in time=704.
taskID=3 finished in time=883.

第二次:

taskID=1 start.
taskID=2 start.
taskID=3 start.
results:
Array(3) [undefined, undefined, undefined]
taskID=2 finished in time=17.
taskID=3 finished in time=212.
taskID=1 finished in time=612.

第三次:

taskID=1 start.
taskID=2 start.
taskID=3 start.
results:
Array(3) [undefined, undefined, undefined]
taskID=3 finished in time=130.
taskID=1 finished in time=256.
taskID=2 finished in time=593.

实验还是要至少做上3次以上才有说服力。

通过输出结果我们能够看出返回的数组内的数据都为undefined。我们就要找出这个原因,那就是找到了为什么要使用箭头函数。

  1. 首先我通过调试来查找
    如图:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZTdOow8-1628238283115)(https://note.youdao.com/yws/api/personal/file/WEBcfbe51c67215e9414c1ac940d99e0caa?method=download&shareKey=c3945e0b4a991911953e4986d51b11cd)]

程序首先打印出了

taskID=1 start.
taskID=2 start.
taskID=3 start.

说明一定是先执行了

console.log(`taskID=${taskID} start.`);

所以我们在这段打上断点进行一步一步调试,如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vVqeaAeA-1628238283117)(https://note.youdao.com/yws/api/personal/file/WEBdae375d2f798007f6390ff75f746943b?method=download&shareKey=3c091febde5a6b7e582e7a261b7b8854)]

根据上图我们可以看出console.log(taskID=${taskID} start.)每次都会被执行,setTimeout也会被执行,但是3次之后,就会直接开始执行.then(),所以我们找到了原因,Promise.all()这时并没有等待返回完整的数据就执行了.then(),没有等到resolve就开始执行了。

说明这里面出现了异常,而这个异常就是由于Promise.all()内的参数,存在函数,造成this混淆,所以我们要使用对象,更准确的说法就是***实例***。

注意:

以这段代码为例:

var p1 = Promise.resolve(1),
    p2 = Promise.resolve(2),
    p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
    console.log(results);  // [1, 2, 3]
});

在上面的方法中,promise数组中所有的promise实例都变为resolve的时候,该方法才会返回,并将所有结果传递results数组中。promise数组中任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的promise对象。reject使用示例如下:

var p1 = Promise.resolve(1),
    p2 = Promise.reject(2),
    p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
    //then方法不会被执行
    console.log(results); 
}).catch(function (e){
    //catch方法将会被执行,输出结果为:2
    console.log(2);
});
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/141893.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • war包压缩命令_抽取zh.pak

    war包压缩命令_抽取zh.pak参考链接:[url]http://www.javaworld.com/article/2857714/learn-java/manipulating-jars-wars-and-ears-on-the-command-line.html[/url]jarcvfaaa.war*jartvfaaa.warjarxvfaaa.war能够作用于jar文件的jar命令同样也…

    2022年10月4日
    0
  • Jetson Nano 从入门到实战(案例:Opencv配置、人脸检测、二维码检测)

    Jetson Nano 从入门到实战(案例:Opencv配置、人脸检测、二维码检测)目录1.JetsonNano简介2.JetsonNano环境配置2.1开箱配件介绍2.2烧录系统2.3开机和基本设置2.4开发环境配置2.4.1更新源和软件2.4.2关屏时间设置2.4.3安装中文输入法2.4.4安装CodeOSS2.4.5安装Qt53.项目案例3.1人脸检测3.1.1安装pip3.1.2…

    2022年6月21日
    20
  • 了解automake和autoconf(autoreconf)[通俗易懂]

    了解automake和autoconf(autoreconf)[通俗易懂]本文转载自《例解autoconf和automake生成Makefile文件》 通过这篇文章可以了解auotmake和autoconf的基本工作流程,文章讲的通俗易懂,但是版本较老。了解新版本的automake可以参考automake的WiKi主页Automake,通过下图可以很清晰的了解auomake和autoconf是如何生成configure脚本文件和最终的makefile文件…

    2022年10月31日
    0
  • android m 滑动解锁,滑动解锁Slideunlock[通俗易懂]

    android m 滑动解锁,滑动解锁Slideunlock[通俗易懂]滑动解锁(Slideunlock)在之前的塞班机上可谓光茫四射,惹得一身荣耀,如今登入android市场,依然备受关注,多种解锁截屏法方式,满足不同人的需求。软件介绍滑动解锁(Slideunlock),一款仿Iphone又超越Iphone解锁和加锁的实用软件,华丽百变的UI,多种感应器加锁解锁功能,是您可以做到无需触碰手机即可轻松完成加锁或解锁操作。此软件的Symbian版曾流行国内外,一度在No…

    2022年6月15日
    38
  • Ubuntu18.04下卸载CUDA11.0

    Ubuntu18.04下卸载CUDA11.0由于深度学习部分代码仅支持CUDA11.0版本之前的Pytorch,Pytorch官网也没有提供与CUDA11.0配套的版本,因此决定卸载CUDA11.0,但是网上也都是11.0版本之前的卸载方式,因此按照网上的方法进行卸载,由于本人linux操作系统不熟,只能自己尝试首先在安装完CUDA11.0版本后,进入CUDA官网准备安装CUDA10.2,本来没想卸载CUDA11.0但在安装的最后一步出现:说明,在存在更高版本(11.0)情况下,无法正常安装,于是进行卸载:为什么不按照网.

    2022年6月17日
    43
  • IP地址分类(A类 B类 C类 D类 E类)「建议收藏」

    IP地址分类(A类 B类 C类 D类 E类)「建议收藏」一、简介IP地址由四段组成,每个字段是一个字节,即4个字节、每个字节有8位,最大值是255(=256:0~255)。全世界范围是唯一的32位(4个字节*8位)的标识符。IP地址由两部分组成,即网络地址和主机地址,二者是主从关系:1.网络号net-id,它标志主机(或路由器)…

    2022年4月29日
    124

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号