Promise的使用

1. 回调地狱

  • 定义:回调函数中 嵌套 回调函数的情况就叫做回调地狱。

  • 优点:回调函数能使 需要异步操作 的代码 按照顺序执行

  • 缺点: 代码的可读性变差!

  • 代码示例

 //地狱回调
 setTimeout(function () {  //第一层
       console.log('张三');//等3秒打印张三在执行下一个回调函数
       setTimeout(function () {  //第二层
           console.log('李四');//等2秒打印李四在执行下一个回调函数
           setTimeout(function () {   //第三层
               console.log('王五');//等一秒打印王五
           }, 1000)
       }, 2000)
   }, 3000)

2. 回调地狱的解决方案

2.1. Promise

2.1.1. Promise 的基本使用

  • Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。
  • Promise 是用户自己创建的,Promise 构造函数接受一个 异步函数(异步任务) 作为参数,该异步函数要有 resolve, reject 两个参数,用于改变 Promise状态 ,与成功与失败的返回值!
  • then 函数是 Promise 的执行函数执行成功是的回调函数,
  • catch 函数是 Promise 的执行函数执行失败是时的回调函数,
  • finally 函数无论函数执行成功或者失败时,finally 都会执行,
  let p = new Promise((resolve, reject) => {
    // resolve('成功')  //走了成功就不会走失败
    // throw new Error('失败了');
    // reject('failed') // 走了失败就不会走成功
    setTimeout(() => {
      resolve('success')
    }, 0)
    
  })
  
  p.then(res => {
    console.log(res);
  }, err => {
    console.log(err);
  }).catch(err => {
    console.log(err);
  }).finally( () =>{
     console.log('finally executor running ....')
  }
  )
  console.log(p);

2.1.2. Promise 源码

class myPromise {
    constructor(executor) {
      this.state = 'pending'; //状态值
      this.value = undefined; //成功的返回值
      this.reason = undefined; //失败的返回值
      this.onResolveCallbacks = []; //成功的回调函数
      this.onRejectCallbacks = []; //失败的回调函数
      //成功
      let resolve = (value) => {
        if (this.state === 'pending') {
          this.state = 'fullFilled'
          this.value = value
          this.onResolveCallbacks.forEach(fn => fn())
        }
      }
      //失败
      let reject = (reason) => {
        if (this.state === 'pending') {
          this.state = 'rejected'
          this.reason = reason
          this.onRejectCallbacks.forEach(fn => fn())
        }
      }
      try {
        //执行函数
        executor(resolve, reject)
      } catch (err) {
        //失败则直接执行reject函数
        reject(err)
      }
    }
    
    then(onFullfilled, onRejected) {
      // then 返回一个promise ,这样就是一个递归
      let promise2 = new myPromise((resolve, reject) => {
        let x;
        if (this.state === 'fullFilled') {
          //同步,状态为fullfilled,执行 onFullfilled,传入成功的值
          setTimeout(() => {
            try {
              x = onFullfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (error) {
              //中间任何一个环节报错都走reject
              reject(error)
            }
          }, 0)//同步无法使用promise2,所以借用setTimeout异步的方式。
        }
  
        if (this.state === 'rejected') {
          //异步,rejected,执行 onRejected,传入失败的值
          setTimeout(() => {
            try {
              x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (error) {
              //中间任何一个环节报错都走reject
              reject(error)
            }
          }, 0)//同步无法使用promise2,所以借用setTimeout异步的方式。
        }
        //异步
        if (this.state === 'pending') {
          this.onResolveCallbacks.push(() => {
            setTimeout(() => {
              try {
                x = onFullfilled(this.value)
                resolvePromise(promise2, x, resolve, reject)
              } catch (error) {
                //中间任何一个环节报错都走reject
                reject(error)
              }
            }, 0)//同步无法使用promise2,所以借用setTimeout异步的方式。
          })
          this.onRejectCallbacks.push(() => {
            setTimeout(() => {
              try {
                x = onRejected(this.reason)
                resolvePromise(promise2, x, resolve, reject)
              } catch (error) {
                //中间任何一个环节报错都走reject
                reject(error)
              }
            }, 0)//同步无法使用promise2,所以借用setTimeout异步的方式。
          })
        }
      })
      return promise2;//返回promise
    }
    finally(callback) {
      return this.then(callback, callback)
    }
    catch(rejectFuc) {
      return this.then(null, rejectFuc);
    }
  }
  
  // 这个方法的主要左右是用来判断x的值,如果x的值是一个普通的值,就直接返回x的值,如果x的值是一个promise,就要返回x.then()执行的结果,核心代码如下
  const resolvePromise = (promise2, x, resolve, reject) => {
    // x和promise2不能是同一个,如果是同一个就报错
    if (promise2 === x) {
      return reject(
        new TypeError('Chaining cycle detected for promise #<promise>')
      )
    }
    // x 为调用链中上一个 promise 中 onFullfilled 方法返回的结果
    // 判断 x 是否是一个对象,判断函数是否是对象的方法有: typeof instanceof constructor toString
    if (typeof x === 'object' && x != null || typeof x === 'function') {
      let called;
      try {
        let then = x.then; //取 then可以报错,报错就走 reject();
        if (typeof then === 'function') {
          then.call(x, y => {
            console.log('y', y);
            if (called) return;
            called = true;
            resolve(y); //采用promise的成功结果,并且向下传递
            resolvePromise(promise2, y, resolve, reject)
          }, r => {
            if (called) return;
            called = true;
            reject(r);//采用promise的成功结果,并且向下传递
          })
        } else {
          resolve(x); //x不是一个函数,是一个对象
        }
      } catch (error) {
        if (called) return;
        called = true;
        reject(error);//报错就走 reject();
      }
    } else {
      // x 不是对象与functin 则 x 是一个普通值, 直接返回
      resolve(x)
    }
  }
  
  myPromise.resolve = (value) => {
    return new myPromise((resolve, reject) => {
      resolve(value)
    })
  }
  myPromise.reject = (value) => {
    return new myPromise((resolve, reject) => {
      reject(value)
    })
  }
  
  // all 方法:全部成功返回一个数组,失败就返回失败的值
  myPromise.all = (values) => {//传入的是一个数组 [p1,p2]
    return new myPromise((resolve, reject) => {
      let results = [];
      let i = 0;
      let processData = (value, index) => {
        results[index] = value;
        i++;
        if (i === values.length) {
          resolve(results)
        }
      }
      for (let i = 0; i < values.length; i++) {
        //获取当前的函数
        let current = values[i];
        //判断是函数还是变量
        if (typeof current === 'function' || (typeof current === 'object' && typeof current !== null)) {
          if (typeof current.then === 'function') {
            current.then(y => {
              processData(y, i)
            }, err => {
              reject(err); //只要有一个传入的promise没有执行成功就走 reject
              return;
            })
          } else {
            //常量
            processData(values[i], i)
          }
        }
      }
    })
  }
  
  // race 方法:先快执行谁,然后返回
  myPromise.race = (values) => {//传入的是一个数组[p1,p2]
    return new myPromise((resolve, reject) => {
      for (let i = 0; i < values.length; i++) {
        let current = values[i];
        if (typeof current === 'function' || (typeof current === 'object' && typeof current !== null)) {
          current.then(y => {
            resolve(y)
            return
          }, err => {
            reject(err)
            return
          })
        } else {
          resolve(current)
        }
      }
    })
  }


2.2. async / await 关键字

2.2.1. async 函数

  • async 函数是函数使用 async 关键字声明的函数

  • async 函数是 AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。

    await关键字只在 async 函数内有效。如果你在 async 函数体之外使用它,就会抛出语法错误 SyntaxError 。

  • async 和 await 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 promise。

  • async 函数可能包含 0 个或者多个 await 表达式。await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。promise 的解决值会被当作该 await 表达式的返回值。使用 async/await 关键字就可以在异步代码中使用普通的 try/catch 代码块。

 function resolveAfter2Seconds() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log(result);
  // Expected output: "resolved"
}

asyncCall();
  • async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。

    例如,如下代码:

       async function foo() {
    	 return 1;
    	}
    

    等价于

    function foo() {
      return Promise.resolve(1);
    }
    
  • async 函数的函数体可以被看作是由 0 个或者多个 await 表达式分割开来的。从第一行代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。这样的话,一个不含 await 表达式的 async 函数是会同步运行的。然而,如果函数体内有一个 await 表达式,async 函数就一定会异步执行。

  • 例如,如下代码:

      async function foo() {
    	  await 1;
    	}
    

    等价于

    function foo() {
       return Promise.resolve(1).then(() => undefined);
    }
    

2.2.2. 含有 await 的 async 函数内部语句执行顺序

  • 在接下来的例子中,我们将使用 await 执行两次 promise,整个 foo 函数的执行将会被分为三个阶段。
  1. foo 函数的第一行将会同步执行,await 将会等待 promise 的结束。然后暂停通过 foo 的进程,并将控制权交还给调用 foo 的函数。
  2. 一段时间后,当第一个 promise 完结的时候,控制权将重新回到 foo 函数内。示例中将会将1(promise 状态为 fulfilled)作为结果返回给 await 表达式的左边即 result1。接下来函数会继续进行,到达第二个 await 区域,此时 foo 函数的进程将再次被暂停。
  3. 一段时间后,同样当第二个 promise 完结的时候,result2 将被赋值为 2,之后函数将会正常同步执行,将默认返回undefined 。
async function foo() {
  const result1 = await new Promise((resolve) =>
    setTimeout(() => resolve("1")),
  );
  const result2 = await new Promise((resolve) =>
    setTimeout(() => resolve("2")),
  );
}
foo();

注意:promise 链不是一次就构建好的,相反,promise 链是分阶段构造的,因此在处理异步函数时必须注意对错误函数的处理。

例如,在下面代码中,即使在 promise 链中进一步配置了 .catch 方法处理,也会抛出一个未处理的 promise 被拒绝的错误。这是因为 p2 直到控制从 p1 返回后才会连接到 promise 链。

async function foo() {
  const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
  const p2 = new Promise((_, reject) => setTimeout(() => reject("2"), 500));
  const results = [await p1, await p2]; // 不推荐使用这种方式,请使用 Promise.all 或者 Promise.allSettled
}
foo().catch(() => {}); // 捕捉所有的错误...

2.2.3. await 串行和并行

function resolveAfter2Seconds() {
    console.log("starting slow promise");
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve("slow");
        console.log("slow promise is done");
      }, 2000);
    });
  }
  
  function resolveAfter1Second() {
    console.log("starting fast promise");
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve("fast");
        console.log("fast promise is done");
      }, 1000);
    });
  }
  
  async function sequentialStart() {
    console.log("==SEQUENTIAL START==");
  
    // 1. Execution gets here almost instantly
    const slow = await resolveAfter2Seconds();
    console.log(slow); // 2. this runs 2 seconds after 1.
  
    const fast = await resolveAfter1Second();
    console.log(fast); // 3. this runs 3 seconds after 1.
  }
  
  async function concurrentStart() {
    console.log("==CONCURRENT START with await==");
    const slow = resolveAfter2Seconds(); // starts timer immediately
    const fast = resolveAfter1Second(); // starts timer immediately
  
    // 1. Execution gets here almost instantly
    console.log(await slow); // 2. this runs 2 seconds after 1.
    console.log(await fast); // 3. this runs 2 seconds after 1., immediately after 2., since fast is already resolved
  }
  
  function concurrentPromise() {
    console.log("==CONCURRENT START with Promise.all==");
    return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then(
      (messages) => {
        console.log(messages[0]); // slow
        console.log(messages[1]); // fast
      },
    );
  }
  
  async function parallel() {
    console.log("==PARALLEL with await Promise.all==");
  
    // Start 2 "jobs" in parallel and wait for both of them to complete
    await Promise.all([
      (async () => console.log(await resolveAfter2Seconds()))(),
      (async () => console.log(await resolveAfter1Second()))(),
    ]);
  }
  
  sequentialStart(); // after 2 seconds, logs "slow", then after 1 more second, "fast"
  
  // wait above to finish
  setTimeout(concurrentStart, 4000); // after 2 seconds, logs "slow" and then "fast"
  
  // wait again
  setTimeout(concurrentPromise, 7000); // same as concurrentStart
  
  // wait again
  setTimeout(parallel, 10000); // truly parallel: after 1 second, logs "fast", then after 1 more second, "slow"

await 和并行
在 sequentialStart 中,程序在第一个 await 停留了 2 秒,然后又在第二个 await 停留了 1 秒。直到第一个计时器结束后,第二个计时器才被创建。程序需要 3 秒执行完毕。

在 concurrentStart 中,两个计时器被同时创建,然后执行 await。这两个计时器同时运行,这意味着程序完成运行只需要 2 秒,而不是 3 秒,即最慢的计时器的时间。

但是 await 仍旧是顺序执行的,第二个 await 还是得等待第一个执行完。在这个例子中,这使得先运行结束的输出出现在最慢的输出之后。

如果你希望并行执行两个或更多的任务,你必须像在parallel中一样使用await Promise.all([job1(), job2()])。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值