深入理解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 相关文章推荐
js简易namespace管理器 实例代码
Jun 21 Javascript
js获取元素外链样式的方法
Jan 27 Javascript
Node.js和MongoDB实现简单日志分析系统
Apr 25 Javascript
jQuery实现图片与文字描述左右滑动自动切换的方法
Jul 27 Javascript
原生js实现可拖拽效果
Feb 28 Javascript
jQuery EasyUI ProgressBar进度条组件
Feb 28 Javascript
详解vue.js的devtools安装
May 26 Javascript
vue-cli项目中怎么使用mock数据
Sep 27 Javascript
vue router 配置路由的方法
Jul 26 Javascript
node.js中express模块创建服务器和http模块客户端发请求
Mar 06 Javascript
Vue表单之v-model绑定下拉列表功能
May 14 Javascript
vue中实现Monaco Editor自定义提示功能
Jul 05 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
复杂检索数据并分页显示的处理方法
2006/10/09 PHP
生成静态页面的PHP类
2006/11/25 PHP
php分页示例代码
2007/03/19 PHP
Mysql中limit的用法方法详解与注意事项
2008/04/19 PHP
分享下页面关键字抓取components.arrow.com站点代码
2014/01/30 PHP
验证坐标在某坐标区域内php代码
2016/10/08 PHP
php获取访问者浏览页面的浏览器类型
2017/01/23 PHP
JavaScript实用技巧(一)
2010/08/16 Javascript
Bookmarklet实现启动jQuery(模仿 云输入法)
2010/09/15 Javascript
关于js拖拽上传 [一个拖拽上传修改头像的流程]
2011/07/13 Javascript
event.currentTarget与event.target的区别介绍
2012/12/31 Javascript
jQuery地图map悬停显示省市代码分享
2015/08/20 Javascript
Bootstrap框架实现广告轮播效果
2016/11/28 Javascript
es7学习教程之fetch解决异步嵌套问题的方法示例
2017/07/21 Javascript
BootStrap 页签切换失效的解决方法
2017/08/17 Javascript
JavaScript数组push方法使用注意事项
2017/10/30 Javascript
JavaScript原型对象、构造函数和实例对象功能与用法详解
2018/08/04 Javascript
浅谈vue 单文件探索
2018/09/05 Javascript
python使用正则搜索字符串或文件中的浮点数代码实例
2014/07/11 Python
Python操作MongoDB数据库PyMongo库使用方法
2015/04/27 Python
Django查找网站项目根目录和对正则表达式的支持
2015/07/15 Python
Python全局变量用法实例分析
2016/07/19 Python
Python端口扫描简单程序
2016/11/10 Python
Python 实现异步调用函数的示例讲解
2018/10/14 Python
对python判断ip是否可达的实例详解
2019/01/31 Python
详解PyTorch中Tensor的高阶操作
2019/08/18 Python
python操作微信自动发消息的实现(微信聊天机器人)
2020/07/14 Python
Python 操作SQLite数据库的示例
2020/10/16 Python
pandas抽取行列数据的几种方法
2020/12/13 Python
大三预备党员入党思想汇报
2014/01/08 职场文书
学校教师安全责任书
2014/07/23 职场文书
2014年党的群众路线整改措施思想汇报
2014/10/12 职场文书
合作协议书范本
2014/10/25 职场文书
500字小学生检讨书
2015/02/19 职场文书
产品调价通知函
2015/04/20 职场文书
Nginx配置https原理及实现过程详解
2021/03/31 Servers