深入学习Promise(二):连锁与合成、异步函数

63 阅读5分钟

本篇粗略地进行阅读总结,还有许多遗漏的地方后续慢慢补上~

promise连锁与合成

promise连锁

  1. 因为每个promise实例的方法 then catch finally 都会返回一个新的promise对象,可以链式调用

  2. 要执行异步任务,可以让每个执行器都返回一个promise实例,让后续promise都等待之前的promise,也就是串行化异步任务

    每个后续的处理程序都会等待前一个promise解决,然后实例化一个新promise并返回它,这种结构可以简洁地将异步任务串行化,解决之前回调的问题

        function delayedResolve(str) {
            return new Promise((resolve, reject) => {
                console.log(str);
                setTimeout(resolve, 1000)
            })
        }
        delayedResolve('1')    // 1s后:1
            .then(() => delayedResolve('2'))  // 2s后:2
            .then(() => delayedResolve('3'))  // 3s后:3
    

Promise.all()

const p = Promise.all([p1, p2, p3]);
  1. 用于将多个Promise实例包装成一个新的Promise实例

  2. 参数

    接受一个数组作为参数 也可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例

  3. p的状态

    只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数,但是并不影响所有promise的拒绝操作,不会有错误跑掉

  4. 注意

    如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法

let p = Promise.all([
    Promise.resolve(1),
    Promise.resolve(),
    Promise.resolve(2)
])
p.then(value => console.log(value))   [1, undefined, 2]

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
// p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved
// 导致Promise.all()方法参数里面的两个实例都会resolved 因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了
// 如果p2没有自己的catch方法,就会调用Promise.all()的catch方法

race()

  1. 也是用于将多个 Promise 实例,包装成一个新的 Promise 实例

  2. 与 all()的区别

    • 不需要等所有实例的状态发生变化 只要有一个状态发生变化 就会把这个实例的返回值传给 race的Promise实例

    • 如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve

    • 如果有一个promise拒绝,只要它是第一个转变状态的,就会成为拒绝合约Promise的理由。之后再拒绝的promise不会影响最终promise的拒绝理由。!这并不影响所有报刊promise正常的拒绝操作。合成的promise会静默处理所有包含promise的拒绝操作,即 不会有错误跑掉,不会有未处理的错误

        let p = Promise.race([
            Promise.reject(3),
            new Promise((resolve, reject) => setTimeout(reject, 1000))
        ])
        p.catch((reason) => console.log(reason))   // 立即输出 3
        // !!! 虽然只有第一个promise的拒绝理由会进入拒绝处理程序,但是第二个promise的拒绝也会被静默处理,不会有错误跑掉
    
  3. 实例

    使用场景 请求超时提醒 如信号不好时发出给提醒

function request() {
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('请求成功')
    },4000)
  })
}
function timeout() {
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      reject('信号不佳,请求超时')
    },3000)
  })
}
Promise.race([request(),timeout()]).then((value)=>{
  console.log(value);
}).catch((reason)=>{
  console.log(reason);
})             // 信号不佳,请求超时

异步函数

也称为 async/await(语法关键字),是ES8规范新增的,让同步方式写的代码可以异步执行

async

  • 该关键字用于声明异步函数,可以用在 函数声明函数表达式箭头函数方法
async function foo() { }
let bar = async function () { };
let baz = async () => { };
class Qux {
    async qux() { }
}
  • 异步函数如果使用return返回了值(没有返回undefined),这个值会被Promise.resolve()包装成一个promise对象,异步函数始终返回promise对象,在函数外部调用这个函数可以得到它返回的promise
async function foo() {
    console.log(1);
    return 3     // 等同于 return Promise.resolve(3)
}
console.log(foo());   // Promise {<fulfilled>: 3}

await

  • 异步函数主要针对不会马上完成的任务,使用await关键字可以暂停异步函数代码的执行,等待promise解决
  • 该关键字会暂停执行函数后面的代码,让出js运行时的执行线程,这个行为和生成器函数中的yield关键字是一样的
  • 可以单独使用,也可以在表达式中使用
  • 单独的Promise.reject()不会被异步函数捕获,而会抛出未捕获错误。不过,对拒绝的promise使用await则会释放错误值(将拒绝promise返回)
async function foo() {
    await new Promise((resolve, reject) => setTimeout(resolve, 2000))
    console.log(111);
}
foo()   // 2s后打印111

// 观察以下输出有什么区别
async function foo() {
    console.log(1);
    await Promise.reject(2)
    console.log(3);   // 这行代码不会执行
}
foo().catch(err => console.log(err))
console.log(4);
// 1
// 4 
// 2

async function foo() {
    console.log(1);
    await Promise.reject(2).catch(err => console.log(err))
    console.log(3);
}
foo()
console.log(4);
// 1
// 4
// 2
// 3
// 调用异步函数foo
//(在foo())中打印1 
//(在foo())await关键字暂停执行
// 向消息队列中添加一个任务 foo()退出
// 打印 4 
// 同步线程执行完毕
// JS运行时从消息队列取出任务,恢复异步函数执行
//(在foo())恢复执行,await catch输出2
//(在foo())打印4
// foo()返回

async function foo() {
    console.log(1);
    Promise.reject(2).catch(err => console.log(err))
    console.log(3);
}
foo()
console.log(4);
// 1
// 3
// 4 
// 2   非重入

实现sleep()

  • 以前该需求都通过setTimeout利用js运行时的行为来实现,有了异步函数之后,一个简单的箭头函数即可实现

    • function sleep(delay) {
          return new Promise((resolve, reject) => setTimeout(resolve, delay))
      }
      async function delayData() {
          console.log(1);
          await sleep(2000)
          console.log(2);
      }
      delayData()    // 1 等待2s后 2