最近在摸鱼的时候,遇到了一个需求:用户可以接连上传多张图片,但是uni.upload一次只能上传一张图片,我当时想着是设置一个Promise,在一个循环中挨个上传,最后做一个整体resolve,但是这样并不合理。后来听得说是可以使用Promise.all方法进行多次请求,但是我只是略懂皮毛。因此决定写下几篇文章,好好学一学Promise的功能与诸多方法。本文将着重介绍Promise的执行顺序和宏任务、微任务概念。

一、基本执行概念

首先了解一些基本概念。

在一个代码段中,我们可以写下输出、赋值等基本语句作为整个脚本的宏任务,也可以写下setTimeout、setInterval等和时间相关的宏任务、还可以通过Promise.then创建微任务。这些任务也有一个相应的优先级:

script语句作为基本宏任务优先直接执行,初次遇到setTimeout等代码将推入宏任务队列,初次遇到promise的内容时,promise内部代码是同步的,因此作为script语句的宏任务正常直接执行,它的.then或者时.catch方法将被压入微任务队列。不过,只有当promise状态不为pedding的时候才可以成功压入队列。

整体流程说来就是:我们输入完成一个代码段后进行运算时,赋值、输出等基本语句以及Promise内部代码时从上往下依次执行,遇到其他宏任务时推入宏任务队列,遇到微任务时推入微任务队列。当一段宏任务代码执行完毕后查看微任务队列中是否有任务,如果有则优先执行全部的微任务,之后再执行下一条宏任务,这段宏任务执行完毕后再检查微任务,再...........循环往复,直至所有任务执行完毕。

了解了基本概念后我们可以看几道例题加深印象。

1.1  promise基础题

1.1.1  题目一

const promise1 = new Promise((resolve, reject) => {console.log('promise1')
})promise1.then(() => {console.log(2);
});console.log('1', promise1);

代码段从上至下,优先执行script语句中new Promise中的同步代码,再执行下面的输出1,由于Promise并未做任何处理,状态还是pedding,因此promise.then并未执行。

最终结果为:

'promise1'
'1' Promise{<pending>}

1.1.2  题目二

const fn = () => (new Promise((resolve, reject) => {console.log(1);resolve('success')
}))
fn().then(res => {console.log(res)
})
console.log('start')

代码段从上至下,首先定义乐了一个函数fn,fn的返回值是一个promise,注意这里是定义,并非执行,所以没有任何输出。然后执行到fn().then语句,需要注意的是,这里是fn(),意思是执行fn函数,那么开始调用fn,输出1并将返回的promise状态改为resolved。在看到.then方法,推入微任务队列,接着执行下面的start输出。执行至此,第一个宏任务执行完毕,接下来查看微任务队列,发现有一个任务输出success微任务队列清空,再查找宏任务队列中没有任务,执行完毕。

最终结果为:

1
'start'
'success'

1.2  promise结合setTimeout

1.2.1 题目一

console.log('start')
setTimeout(() => {console.log('time')
})
Promise.resolve().then(() => {console.log('resolve')
})
console.log('end')

代码段从上至下,先输出start,遇到setTimeout时将其推入宏任务队列,遇到Promise.resolve().then()的时候将其推入微任务队列,然后输出end。当前宏任务执行完毕,开始检查有无微任务,有,输出resolve。清空微任务队列后,再检查宏任务队列有无任务,有,输出time。该宏任务执行完毕后检查有无微任务,无;再检查有无宏任务,无,执行完毕。

最终结果为:

'start'
'end'
'resolve'
'time'

1.2.2 题目二

const promise = new Promise((resolve, reject) => {console.log(1);setTimeout(() => {console.log("timerStart");resolve("success");console.log("timerEnd");}, 0);console.log(2);
});
promise.then((res) => {console.log(res);
});
console.log(4);

代码从上至下,牢记!Promise内部的代码仍然是同步执行代码,它的then和catch才会被推入微任务队列。因此输出1,将setTimeout推入宏任务队列,再输出2。接下来看到Promise.then,然而我们此时并不能将其推入微任务队列,因为它此时还是pedding状态,定义它的promise还并未执行resolve或者是reject,先不执行它。因此接下来输出的是4。接下来查看微任务队列,无,再查看宏任务,有,输出timerStart,将该promise状态改为resolved并将之前的promise.then推入微任务队列,输出timerEnd。该宏任务执行完毕。执行完一段宏任务后查看微任务队列,有,输出success。

最终结果为:

1
2
4
"timerStart"
"timerEnd"
"success"

1.2.3  题目三

const promise1 = new Promise((resolve, reject) => {setTimeout(() => {resolve("success");console.log("timer1");}, 1000);console.log("promise1里的内容");
});
const promise2 = promise1.then(() => {throw new Error("error!!!");
});
console.log("promise1", promise1);
console.log("promise2", promise2);
setTimeout(() => {console.log("timer2");console.log("promise1", promise1);console.log("promise2", promise2);
}, 2000);

需要注意的是,这里和1.1.2有所不同。在1.1.2中代码是const fn = () => (new Promise(),意思是在调用fn时返回一个promise,但在这里是promise1 = new promise(),根据之前介绍的,这里promise里的同步代码正常进行。

代码从上至下,遇到setTimeout,放入宏任务队列,接下来输出"promise1里的内容";再往下,promise2这一行要使用到promise1.then,由于1的状态是pedding,因此无法执行。再往下,输出"promise1 <pedding>"和"promise2 <pedding>";再往下遇到第二个setTimeout,放入宏任务队列。至此,第一轮的宏任务执行完毕。

在执行完一段宏任务后检查是否有微任务,无,执行宏任务队列中第一个任务。因此将promise1的状态就改为resolved并将之前的promise2推入微任务队列,再输出timer1。该宏任务执行完毕,查看微任务队列,有,进行err的输出。微任务队列全部执行完毕后,执行下一个宏任务队列,输出timer2,"promise1 <resolved>”,"promise2 <reject>",执行完毕。

最终结果为:

'promise1里的内容'
'promise1' Promise{<pending>}
'promise2' Promise{<pending>}
'timer1'
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
'timer2'
'promise1' Promise{<resolved>: "success"}
'promise2' Promise{<rejected>: Error: error!!!}

1.3  Promise中的then、catch、finally

进行这部分的题目练习前需要看一些概念。

1.3.1  题目一

const promise = new Promise((resolve, reject) => {reject("error");resolve("success2");
});
promise
.then(res => {console.log("then1: ", res);}).then(res => {console.log("then2: ", res);}).catch(err => {console.log("catch: ", err);}).then(res => {console.log("then3: ", res);})

promise的状态只能改变一次,因此执行了reject后便不会执行接下来的resolve。又因为catch不管被连接到哪里,都能捕获上层未捕捉过的错误,于是接来下进入catch方法进行错误的捕获。在执行完catch后会接着执行下面的then3,因为.then.catch都会返回一个新的Promise。但是由于catch返回的promise没有返回值,所以打印出来的是undefined。

最终结果为:

"catch: " "error"
"then3: " undefined

1.3.2  题目二

const promise = new Promise((resolve, reject) => {setTimeout(() => {console.log('timer')resolve('success')}, 1000)
})
const start = Date.now();
promise.then(res => {console.log(res, Date.now() - start)
})
promise.then(res => {console.log(res, Date.now() - start)
})

  Promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值。  如果足够快的话,也可能做到两个都是1001。

最终结果为:

'timer'
'success' 1001
'success' 1002

1.3.3  题目三

Promise.resolve().then(() => {return new Error('error!!!')
}).then(res => {console.log("then: ", res)
}).catch(err => {console.log("catch: ", err)
})

也许这看起来像是在原promise中返回了一个Error,然后执行catch。但是,不要被表面的Error迷惑了,这里是return Error,返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!'))

最终结果为:

"then: " "Error: error!!!"

所以,如果是想要抛出一个错误,推荐用一下两种方法

return Promise.reject(new Error('error!!!'));
// or
throw new Error('error!!!')

1.3.4  题目四

function promise1 () {let p = new Promise((resolve) => {console.log('promise1');resolve('1')})return p;
}
function promise2 () {return new Promise((resolve, reject) => {reject('error')})
}
promise1().then(res => console.log(res)).catch(err => console.log(err)).finally(() => console.log('finally1'))promise2().then(res => console.log(res)).catch(err => console.log(err)).finally(() => console.log('finally2'))

解析:

         这里主要想介绍如果是连续的.then方法,在一次宏任务中只会执行一次,在执行完第一个then方法后,后续的then会被推入微任务队列中,可以理解为链式调用后面的内容需要等前一个调用执行完才会执行,就像是这里的finally()会等promise1().then()执行完才会将finally()加入微任务队列。

最终结果为:

'promise1'
'1'
'error'
'finally1'
'finally2'

1.4  Promise中的all和race

通俗来说,.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调;.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。

1.4.1 题目一

function runAsync (x) {const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))return p
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))

假设现在要处理三个数据的Promise,首先定义了一个函数renAsync,然后在这个promise里面抽象化某一个数据的执行方法。然后进入Promise.all语句。根据概念可知,all方法里面可以包裹多个Promise,因此将多个Promise放在数组[runAsync(1), runAsync(2), runAsync(3)]中,最后可以将他们的结果以数组的格式展现出来。有了all,我们就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。

最终结果为:

// 在间隔一秒后同时打印出1,2,3,还有一个数组[1,2,3]
1
2
3
[1, 2, 3]

1.5 async/await

1.5.1 题目一

async function async1() {console.log("async1 start");await async2();console.log("async1 end");
}
async function async2() {console.log("async2");
}
async1();
console.log('start')

代码从上往下看,定义了两个函数先不用管,看到调用了async1,执行。输出async1 start后看到await,意思是等待async2函数的执行,因此输出async2。需要注意,在这执行完后跳出async1,执行宏任务的同步代码start。在这一轮宏任务执行完毕后再来执行await后面的async1 end。我们可以理解为紧跟着await后面的语句相当于放到了new Promise中(await async2),下一行及之后的语句相当于放在Promise.then中(console.log(async1 end))

async function async1() {console.log("async1 start");// 原来代码// await async2();// console.log("async1 end");// 转换后代码new Promise(resolve => {console.log("async2")resolve()}).then(res => console.log("async1 end"))
}
async function async2() {console.log("async2");
}
async1();
console.log("start")

最终结果为:

'async1 start'
'async2'
'start'
'async1 end'

如果将这里的await async2换成一个new Promise

async function async1() {console.log("async1 start");new Promise(resolve => {console.log('promise')})console.log("async1 end");
}
async1();
console.log("start")
'async start'
'promise'
'async1 end'
'start'

我们可以清晰的看到,promise并不会阻塞后面同步代码end的执行。对比一下我们可以加深对await的理解:await的目标函数会被转化为一个new promise,该行语句的后面语句将会作为.then方法推入微任务队列中执行。需要注意的是,await语句的末尾在转换过后要添加上resolve()来改变.then方法中的状态。

1.5.2 题目二

async function async1() {console.log("async1 start");await async2();console.log("async1 end");
}
async function async2() {setTimeout(() => {console.log('timer')}, 0)console.log("async2");
}
async1();
console.log("start")

和题目一差不多类型,这次添加上了定时器,我们还是可以将这个await换成promise理解。

async function async1() {console.log("async1 start");new Promise(resolve=>{setTimeout(() => {console.log('timer')}, 0)resolve()console.log("async2");}).then(()=>{console.log("async1 end");})
async1();
console.log("start")

当async1执行的时候首先输出start,遇到promise的同步任务,进去执行。遇到setTimeout,推入宏任务队列,继续往下遇到async2输出。之后再接着script语句的宏任务往下走输出start。这一轮宏任务执行完毕查看微任务队列,遇到已经被resolve后的then,输出end。再查看宏任务队列,发现定时器的任务,输出timer。

最终结果为:

'async1 start'
'async2'
'start'
'async1 end'
'timer'

1.5.3 题目三

async function async1() {console.log("async1 start");await async2();console.log("async1 end");setTimeout(() => {console.log('timer1')}, 0)
}
async function async2() {setTimeout(() => {console.log('timer2')}, 0)console.log("async2");
}
async1();
setTimeout(() => {console.log('timer3')
}, 0)
console.log("start")

这里做一道综合一点的题,也许一时半会还不能肉眼判别,所以还是来进行转换。

async function async1() {console.log("async1 start");new Promise(resolve=>{setTimeout(() => {console.log('timer2')}, 0)console.log("async2");resolve()}).then(resolve=>{console.log("async1 end");setTimeout(() => {console.log('timer1')}, 0)})
}async1();
setTimeout(() => {console.log('timer3')
}, 0)
console.log("start")

最终结果为:

'async1 start'
'async2'
'start'
'async1 end'
'timer2'
'timer3'
'timer1'

1.5.4 题目四

async function async1 () {console.log('async1 start');await new Promise(resolve => {console.log('promise1')resolve('promise1 resolve')}).then(res => console.log(res))console.log('async1 success');return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

这里将计时器换成了一个promise,我们再来转换看看。

async function async1 () {console.log('async1 start');await new Promise(resolve => {console.log('promise1')resolve('promise1 resolve')}).then(res => console.log(res)).then(resolve=>{console.log('async1 success');resolve()return 'async1 end'})
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

最终结果为:

'script start'
'async1 start'
'promise1'
'script end'
'promise1 resolve'
'async1 success'
'async1 end'

1.5.5 题目五

async function async1 () {console.log('async1 start');await new Promise(resolve => {console.log('promise1')resolve('promise resolve')})console.log('async1 success');return 'async1 end'
}
console.log('srcipt start')
async1().then(res => {console.log(res)
})
new Promise(resolve => {console.log('promise2')setTimeout(() => {console.log('timer')})
})

继续转换。

async function async1 () {    // 2console.log('async1 start');    // 3await new Promise(resolve => {console.log('promise1')    // 4resolve('promise resolve') // 5 转换该promise的状态}).then(resolve=>{    // 6 推入微任务队列 //10 第一轮宏任务结束,执行该微任务console.log('async1 success');    // 11return 'async1 end'    //12 返回新的promise.resolve('async1 end')})
}console.log('srcipt start')    // 1async1().then(res => {    // 2 调用async1() //7 pedding状态保持不变,不推入队列console.log(res)    //13 状态改变为resolve
})new Promise(resolve => {console.log('promise2')    // 8setTimeout(() => {         // 9 推入宏任务队列console.log('timer')     // 14})
})

最终结果为:

'script start'
'async1 start'
'promise1'
'promise2'
'async1 success'
'async1 end'
'timer'

1.5.6 题目六

async function async1() {console.log("async1 start");await async2();console.log("async1 end");
}async function async2() {console.log("async2");
}console.log("script start");setTimeout(function() {console.log("setTimeout");
}, 0);async1();new Promise(function(resolve) {console.log("promise1");resolve();
}).then(function() {console.log("promise2");
});
console.log('script end')

还是继续转换。

async function async1() {    // 3console.log("async1 start");    // 4await async2();    // 5 console.log("async1 end");    // 6 推入微任务队列    // 11
}async function async2() {console.log("async2");    // 5 同步执行
}console.log("script start");    // 1setTimeout(function() {    // 2 推入宏任务队列console.log("setTimeout");    // 13
}, 0);async1();    // 3new Promise(function(resolve) {    console.log("promise1");    // 7resolve();    // 8 改变当前promise状态
}).then(function() {    // 9 推入微任务队列console.log("promise2");    // 12
});
console.log('script end')    // 10

当你做出了前面的题目后再来看这道题,会感觉有些轻松。不过这道题是头条的一道面试题喔。

1.5.7 题目七

为这一系列的题目收个尾。

async function testSometing() {console.log("执行testSometing");return "testSometing";
}async function testAsync() {console.log("执行testAsync");return Promise.resolve("hello async");
}async function test() {console.log("test start...");const v1 = await testSometing();console.log(v1);const v2 = await testAsync();console.log(v2);console.log(v1, v2);
}test();var promise = new Promise(resolve => {console.log("promise start...");resolve("promise");
});
promise.then(val => console.log(val));console.log("test end...");

也许熟练了可以直接答出来,不过我们这里还是做一个转换。

async function test() {    // 1console.log("test start...");    // 2const v1 = new Promise(resolve=>{console.log("执行testSometing");    // 3return "testSometing";    // 4 将当前promise状态改为resolveresolve()}).then(res=>{    // 5 推入微任务队列console.log(v1);    // 10 const v2 = new Promise(resolve=>{console.log("执行testAsync");    // 11return Promise.resolve("hello async");    // 12resolve()}).then(res=>{    // 13 推入微任务队列console.log(v2);    // 15console.log(v1, v2);    // 16})})
}test();    // 1var promise = new Promise(resolve => {console.log("promise start...");    // 6resolve("promise");    // 7 将当前promise状态改为resolve
});promise.then(val => console.log(val));    // 8 推入微任务队列    // 14console.log("test end...");    // 9

最好的理解方式就是看文档或者做题,当把这些题刷完后对于promise的理解可以更加深刻。

参考文章:掘金

Promise与宏任务、微任务相关推荐

  1. 一道透解promise与宏/微任务的面试题

    文章目录 前言 问题题目 问题分析 消息队列 宏任务 微任务(队列) 回调函数延迟绑定 错误冒泡 返回值穿透 解析思路 小结 扩展 结语 前言 又到了一年一度的七夕节,如果大家没有去撒狗粮,那不妨让我 ...

  2. JS事件循环机制:同步与异步任务 之 宏任务 微任务

    JS事件循环机制:同步与异步任务 之 宏任务 微任务 阅读目录 javascript事件循环 setTimeout和setInterval中的执行时间 宏任务和微任务 javascript是单线程,一 ...

  3. 助力白纸一般的你面试——宏任务微任务

    今天我不讲原理只讲实战: 面试中你经常会遇到面试官在宏任务和微任务上对你进行各种刁难,但宏任务微任务你都不明白,你可以不用学前端了,洗洗睡吧!!! JS单线程之类的话我就不讲了,是个人应该都是知道哈( ...

  4. 精学JS:宏任务 微任务的运行机制

    首先分析宏任务和微任务的运行机制,并针对日常开发中遇到的各种宏任务&微任务的方法,结合一些例子来看看代码运行的顺序逻辑,把这部分知识点重新归纳和梳理.   在日常开发中,例如 setTimeo ...

  5. 引擎进阶(上):探究宏任务 微任务的运行机制

    引擎进阶(上):探究宏任务 & 微任务的运行机制   首先分析宏任务和微任务的运行机制,并针对日常开发中遇到的各种宏任务&微任务的方法,结合一些例子来看看代码运行的顺序逻辑,把这部分知 ...

  6. AV转USB;S-Video转USB宏晶微视频采集卡方案

    宏晶微的MS2100和MS2100E支持无损采集,MS2106和MS2106S支持无损和压缩采集,详细功能对比见下表

  7. 记了老是忘记那就写下来吧宏任务微任务

    宏任务:script 定时器 微任务:promiss process.nexttick new Promise(function(resolve){console.log(3);//此为同步程序res ...

  8. 事件循环-宏任务-微任务

    概述 Javascript是一门单线程的脚本语言 时间循环(EventLoop)是JavaScript的运行机制 首先我们先看一下以下代码 setTimeout(function() {console ...

  9. 微平均 宏平均 微平均(准确率、召回率、f1-score相等) 以及 TP、TN、FP、FN的理解

    微平均.宏平均: 一种计算方法是把所有类别的一次性都考虑进来,计算类别预测的准确率.(微平均) 另外还有一种是对每个类别分开考虑,计算单独每个类别的准确率,最后再进行算术平均得到该测试集的准确率.(宏 ...

最新文章

  1. 你说 Arthas 诊断问题不好用?watch 命令了解多少?
  2. python语言能干什么-学Python语言可以做什么?
  3. 组件skype服务器,Skype for Business Server 中的中介服务器组件
  4. Android Telephony分析(六) ---- 接口扩展(实践篇)
  5. java标识符遵循规范
  6. delphi如何让程序最小化到任务栏(使用Shell_NotifyIcon API函数)
  7. PHP学习总结(函数、数组、字符串)
  8. (数据科学学习手札30)朴素贝叶斯分类器的原理详解Python与R实现
  9. 在centOS7.2里安装virtualenv和flask
  10. Druid:数据库连接池实现技术,由阿里巴巴提供的
  11. 超级签名源码_TestFlight 为什么那么多人选择TF签名?
  12. uboot加载linux内核加载那些内容,uBoot和Linux内核中涉及到的几个地址参数的理解...
  13. pygame精灵组有哪些方法_利用 pygame 开发一款游戏:「跳跳兔」(六)
  14. python海龟图画龙珠_DeepOps的Python小笔记-天池龙珠计划-Python训练营-Task 02:DAY5
  15. 单片机中断函数的编写
  16. 42.Linux/Unix 系统编程手册(下) -- 共享库高级特性
  17. 现代互联网的TCP拥塞控制(CC)算法评谈
  18. IOS中impactor报81错误解决方法
  19. python我的世界给予物品指令_我的世界指令:强大的 /give 指令
  20. python 高精度时间_如何基于Python代码实现高精度免费OCR工具

热门文章

  1. 集群服务器session同步
  2. GD32F310k_flash
  3. MySQL如何快速恢复单表
  4. 上半年薪资统计,数据岗中位数接近20K!
  5. html中,让多个div横向排列且不换行(左右滑动)
  6. Vue 3的企业级项目开发开篇词|为何掌握了技术API,依然在项目中处处掣肘?
  7. ubuntu切换用户账户快捷键
  8. tp5.1开发手册链接
  9. You could try using --skip-broken to work around the problem
  10. DSMall多店铺商城B2B2C功能列表清单