深入理解Promise.all


Posted in Javascript onAugust 08, 2018

异步之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) {
 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。我们就要找出这个原因,那就是找到了为什么要使用箭头函数。

首先我通过调试来查找

如图:

深入理解Promise.all

程序首先打印出了

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

说明一定是先执行了

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

所以我们在这段打上断点进行一步一步调试,如下:

深入理解Promise.all

根据上图我们可以看出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);
});

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript实现点击后变换按钮显示文字的方法
May 13 Javascript
jQuery操作iframe中js函数的方法小结
Jul 06 Javascript
js实现添加可信站点、修改activex安全设置,禁用弹出窗口阻止程序
Aug 17 Javascript
JS实现JSON.stringify的实例代码讲解
Feb 07 Javascript
jQuery选择器之子元素选择器详解
Sep 18 jQuery
详谈js中标准for循环与foreach(for in)的区别
Nov 02 Javascript
Angular5.1新功能分享
Dec 21 Javascript
js事件on动态绑定数据,绑定多个事件的方法
Sep 15 Javascript
fetch 如何实现请求数据
Dec 20 Javascript
LayUi使用switch开关,动态的去控制它是否被启用的方法
Sep 21 Javascript
vue+高德地图实现地图搜索及点击定位操作
Sep 09 Javascript
Vue+Openlayers自定义轨迹动画
Sep 24 Javascript
vue js秒转天数小时分钟秒的实例代码
Aug 08 #Javascript
vue devtools的安装与使用教程
Aug 08 #Javascript
jQuery AJAX 方法success()后台传来的4种数据详解
Aug 08 #jQuery
通过jquery的ajax请求本地的json文件方法
Aug 08 #jQuery
Vue 开发音乐播放器之歌手页右侧快速入口功能
Aug 08 #Javascript
jQuery中ajax请求后台返回json数据并渲染HTML的方法
Aug 08 #jQuery
Vue2.0 实现歌手列表滚动及右侧快速入口功能
Aug 08 #Javascript
You might like
MYSQL环境变量设置方法
2007/01/15 PHP
基于PHP的简单采集数据入库程序
2014/07/30 PHP
PHP管理依赖(dependency)关系工具 Composer的自动加载(autoload)
2014/08/18 PHP
php通过Chianz.com获取IP地址与地区的方法
2015/01/14 PHP
浅谈PHP中关于foreach使用引用变量的坑
2016/11/14 PHP
在jQuery中 关于json空对象筛选替换
2013/04/15 Javascript
如何用javascript计算文本框还能输入多少个字符
2015/07/29 Javascript
JavaScript仿静态分页实现方法
2015/08/04 Javascript
jQuery实现的简单折叠菜单(折叠面板)效果代码
2015/09/16 Javascript
深入浅析AngularJS中的module(模块)
2016/01/04 Javascript
js文件中直接alert()中文出来的是乱码的解决方法
2016/11/01 Javascript
详解JS中定时器setInterval和setTImeout的this指向问题
2017/01/06 Javascript
微信小程序图片选择、上传到服务器、预览(PHP)实现实例
2017/05/11 Javascript
javascript填充默认头像方法
2018/02/22 Javascript
element-ui 表格实现单元格可编辑的示例
2018/02/26 Javascript
深入了解Hybrid App技术的相关知识
2019/07/17 Javascript
Bootstrap实现前端登录页面带验证码功能完整示例
2020/03/26 Javascript
vue项目查看vue版本及cli版本的实现方式
2020/10/24 Javascript
python中zip和unzip数据的方法
2015/05/27 Python
Python3中使用PyMongo的方法详解
2017/07/28 Python
Python使用flask框架操作sqlite3的两种方式
2018/01/31 Python
解决python读取几千万行的大表内存问题
2018/06/26 Python
python微信撤回监测代码
2019/04/29 Python
python scatter函数用法实例详解
2020/02/11 Python
通过Django Admin+HttpRunner1.5.6实现简易接口测试平台
2020/11/11 Python
Pycharm 跳转回之前所在页面的操作
2021/02/05 Python
HTML5中实现拖放效果无须借助javascript
2012/12/26 HTML / CSS
世界上最大的在线汽车租赁预订平台:Rentalcars.com(支持中文)
2018/10/12 全球购物
大学本科毕业生求职简历的自我评价
2013/10/09 职场文书
安全大检查实施方案
2014/02/22 职场文书
收银出纳员岗位职责
2014/02/23 职场文书
开学典礼主持词
2014/03/19 职场文书
走群众路线剖析材料
2014/10/09 职场文书
数学教师个人总结
2015/02/06 职场文书
食品质检员岗位职责
2015/04/08 职场文书
Redis高可用集群redis-cluster详解
2022/03/20 Redis