ES6异步Promise

Promise

问题:Promise是什么?

是一个异步问题同步化解决方案

问题:Promise存在的意义是什么?

是一个异步问题同步化解决方案,目的为了把异步任务(如ajax请求),实现同步化之后避免会堵塞后面的程序并拿到结果,顺便解决回调地狱

问题:Promise是如何设计的?

设计为Promise执行是同步而promise.then()是异步执行的

问题:为什么Promise执行是同步而promise.then()是异步执行的?

如果promise.then()是同步执行时是不合理的,且会阻塞下面程序的执行

试打印Promise实例对象

  1. console.log(new Promise(function (resolve, reject) { }));
  2. /**
  3. * Promise {<pending>}
  4. * __proto__: Promise
  5. * [[PromiseState]]: "pending"
  6. * [[PromiseResult]]: undefined
  7. */

Promise实例对象参数是回调函数,名称叫executor执行者

  1. function (resolve, reject) { })

同步执行顺序

  1. new Promise(function (resolve, reject) {
  2. console.log('111');
  3. })
  4. console.log('222');
  5. //先打印111,后222

异步操作的特征:

  • 特征一:具有共有的状态,且状态不受外界影响

    • 进行中 - pending
    • 已成功 - fullfilled(resolve)
    • 已失败 - reject
  • 特征二:状态的不可逆性

    • promise固化以后,再对promise对象添加回调,是可以直接拿到结果的
    • 如果是事件的话,一旦错过就错过了

同步执行的代码如何异步表现操作呢?

  1. //执行executor函数里第一个参数(resolve)或第二个参数(reject)改变状态为resolve/reject
  2. //当执行resolve()/reject()之后,执行绑定的成功/失败后所对应的回调函数
  3. //绑定的回调函数可以通过Promise实例对象里的then()方法来写成功/失败后的程序
  4. let promise = new Promise(function(resolve,reject){
  5. Math.random() * 100 > 60 ? resolve('ok') : reject('no');
  6. });
  7. //promise.then(注册成功的回调函数,注册失败的回调函数);
  8. promise.then((val)=>{console.log(val)}, (err)=>{console.log(err)});

同步/异步执行顺序

  1. let promise = new Promise(function (resolve, reject) {
  2. console.log('111');
  3. resolve('333');
  4. })
  5. promise.then((data) => console.log(data))
  6. console.log('222');
  7. //打印111,222,333
  8. //说明执行主线程同步然后其他同步,再执行异步,最后执行定时器

宏任务队列/微任务队列:

在JS异步代码中,存在宏任务队列和微任务队列

  • 宏任务:定时器
  • 微任务:resolve/reject的回调函数then()

执行顺序:

同步任务 -> 事件轮巡 -> 主线程全部任务完成 -> 调用任务队列当中的回调函数推入到执行栈中,存在优先权的问题:

  1. 微任务/微任务中的回调函数先执行(then())
  2. 再执行宏任务
  1. //微任务
  2. Promise.resolve().then(() => {
  3. console.log('promise1');
  4. //宏任务
  5. setTimeout(() => {
  6. console.log('setTimeout2');
  7. })
  8. })
  9. //宏任务
  10. setTimeout(() => {
  11. console.log('setTimeout1');
  12. //微任务
  13. Promise.resolve().then(() => {
  14. console.log('promise2');
  15. })
  16. })
  17. //打印结果:
  18. //promise1
  19. //setTimeout1
  20. //promise2
  21. //setTimeout2
  22. //执行顺序:这里没有同步任务,只有异步任务
  23. //1.微任务优先执行,打印promise1
  24. //2.往下执行遇到异步任务setTimeout,挂起到webApis中(等待毫秒)
  25. //3.往下执行遇到宏任务setTimeout,执行并打印setTimeout1
  26. //4.事件第二轮循环,优先执行微任务打印promise2
  27. //5.最后打印setTimeout2

链式调用会存在什么问题?

  1. let promise = new Promise(function (resolve, reject) {
  2. resolve('ok');
  3. })
  4. promise
  5. .then((data) => console.log(data)) //ok
  6. .then((data2) => console.log('then2:' + data2)) //then2:undefined
  7. //说明只能通过第一次then拿到resolve()传入的数据,第二次then却拿不到数据(包括成功/失败)
  1. //解决:手动在第一次then()回调函数里return数据
  2. //说明:第一次then的返回值作为下一次then的参数
  3. promise
  4. .then((data) => {
  5. console.log(data);
  6. return 'nice';
  7. });
  8. //ok
  9. .then((data2) => console.log('then2:' + data2))
  10. //then2:nice
  1. //既然第一次then的返回值作为下一次then的参数
  2. //可以将返回值设定为new Promise(),并传入executor回调且调用resolve()传入参数
  3. promise
  4. //备注:reject()跟resolve()操作一样,不做演示
  5. .then((data) => {
  6. console.log(data);
  7. return new Promise((resolve, reject) => {
  8. resolve('第二次then得到的参数');
  9. });
  10. }) //打印:ok
  11. .then((data2) => console.log('then2:' + data2))
  12. //打印:then2:第二次then得到的参数

问题1:遇到错误的情况

  1. let promise = new Promise(function (resolve, reject) {
  2. resolve(a);
  3. })
  4. promise.then(
  5. (val) => console.log(val),
  6. (err) => console.log(err)
  7. )
  8. //ReferenceError: a is not defined
  9. //哪怕是resolve()传入未定义的变量导致走then()第二个函数打印错误
  10. //executor回调函数抛出的错误被then()里的错误函数马上捕获

只捕捉错误的写法

  1. //写法一:
  2. promise.then(null, (err) => console.log(err));
  3. //写法二:
  4. promise.catch((err) => console.log(err));

由上面的写法延申为以下的写法:

  1. //推荐写法:适用于成功/失败的情况
  2. promise
  3. .then(() => {})
  4. .catch(() => {})

问题2:一旦固化,状态不再改变

  1. let promise = new Promise((resolve, reject) => {
  2. //状态:成功
  3. //注意:执行resolve()意味着固化了状态
  4. resolve('success');
  5. //未定义变量抛出错误
  6. console.log(a);
  7. })
  8. promise
  9. .then((val) => console.log(val))
  10. .catch((err) => console.log(err))
  11. //打印:success
  12. //说明状态固化了只打印resolve的值而不是捕获错误

问题3:链式调用注入数据有可能导致数据丢失

  1. //备注1:不管catch()前面有多少个then(),一旦出现错误都能捕获到(冒泡特性)
  2. //备注2:then()不传数据会丢失数据
  3. promise
  4. .then(() => {})
  5. .then()
  6. .then()
  7. .catch(() => {})

catch()总结:冒泡特性,状态固定后无法捕获错误

问题4:先执行的固化成功状态后执行reject()会冲突吗?

  1. const p1 = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. //3秒后才执行reject(),然后状态改为失败
  4. reject('error');
  5. }, 3000)
  6. });
  7. const p2 = new Promise((resolve, reject) => {
  8. setTimeout(() => {
  9. //先执行resolve且固化状态为成功
  10. //异步p2依赖p1导致状态无效
  11. //但是直接把实例对象p1传进来会导致状态无效
  12. resolve(p1);
  13. }, 1000);
  14. });

异步关系管理

Promise.all():管理多个promise实例对象并包装成一个新的实例

Promise.race():管理多个promise实例对象并包装成一个新的实例,并返回第一个返回的数据

  1. //Promise.all()处理多个Promise异步操作且拿到多个Promise所返回的值
  2. //以数组或具有iterator数据接口方式传多个值
  3. //引入文件模块
  4. const fs = require('fs');
  5. let p1 = new Promise((resolve, reject) => {
  6. fs.readFile('./name.txt', 'utf-8', (err, data) => {
  7. //更改为成功状态并把读取到的data传入
  8. resolve(data);
  9. })
  10. })
  11. let p2 = new Promise((resolve, reject) => {
  12. fs.readFile('./number.txt', 'utf-8', (err, data) => {
  13. //更改为成功状态并把读取到的data传入
  14. resolve(data);
  15. })
  16. })
  17. let p3 = new Promise((resolve, reject) => {
  18. fs.readFile('./score.txt', 'utf-8', (err, data) => {
  19. //更改为成功状态并把读取到的data传入
  20. resolve(data);
  21. })
  22. })
  23. //拿到成功的结果
  24. const p = Promise.all([p1, p2, p3]);
  25. console.log(p);
  26. //打印一个Promise{}对象
  27. p.then((res) => console.log(res));
  28. // ['./name.txt','./number.txt','./score.txt']
  29. //总结:Promise.all()一旦出现出错,返回第一个出错的报错(不演示)

Promisify

Promise

通过new Promise方式将异步操作包装一下

  1. function readFile(path) {
  2. return new Promise((resolve, reject) => {
  3. fs.readFile(path, 'utf-8', (err, data) => {
  4. resolve(data);
  5. })
  6. })
  7. }
  8. readFile('./name.txt')
  9. .then(data => readFile(data))
  10. .then(data => readFile(data))
  11. .then(data => console.log(data));

写一个针对所有异步操作的函数

  1. function promisify(fn) {
  2. //收集函数执行的参数
  3. return function (...args) {
  4. return new Promise((resolve, reject) => {
  5. //执行原本的函数,且传入原本的参数以及要处理的最后的回调函数
  6. fn(...args, (err, data) => {
  7. //判断什么时候resolve/reject
  8. if (err) {
  9. reject(err);
  10. } else {
  11. resolve(data);
  12. }
  13. })
  14. })
  15. }
  16. }
  17. //调用
  18. let readFile = promisify(fs.readFile);
  19. readFile('./name.txt', 'utf-8')
  20. .then(data => readFile(data, 'utf-8'))
  21. .then(data => readFile(data, 'utf-8'))
  22. .then(data => console.log(data));

fs模块上的所有方法转为promisify方法

  1. //格式:fs.writeFile => fs.writeFileAsync
  2. function promisifyAll(obj) {
  3. for (let [key, fn] of Object.entries(obj)) {
  4. if (typeof fn === 'function') {
  5. obj[key + 'Async'] = promisify(fn);
  6. }
  7. }
  8. }

async/await

适用于异步函数的ES6语法

本质上也是一个语法糖,来源于生成器函数

  1. 内置的执行器(co函数)
  2. 更好的语义
  3. 更广的实用性

async的返回值是一个Promise对象,也有三种状态,每种状态对应每种回调