Promise、async/await、Generator 异步解决方案

参考: https://www.cnblogs.com/CandyManPing/p/9384104.html  或  https://www.jianshu.com/p/fe0159f8beb4(推荐这个,比较清晰)


 Promise :

参考:https://www.jianshu.com/p/fe0159f8beb4  或 https://www.runoob.com/w3cnote/es6-promise.html

1、Promise 介绍 :Promise 实例 只是一个对象而不是方法,参数是一个回调函数。创建对象时立即执行,因为Promise 只是改变一个异步函数的结构,不会改变异步函数的执行时机。

  • 普通异步程序
    setTimeout(() => {
        console.log('定时器异步程序');
    }, 1000)
  • 通过 Promise 改造,变成
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
        }, 1000)
    }).then(() => {
        console.log('定时器异步程序');  // 异步执行的程序全部放这里了
    })
  • 使用Promise 封装 ajax:
    因为 new Promise 里面的函数是立即执行的,而我们封装的接口,需要调用后才去执行。所以我们需要通过函数  return 一个promise 就可以了。
    function ajax(URL) {
        return new Promise(function (resolve, reject) {   // return 一个 promise对象
            var req = new XMLHttpRequest(); 
            req.open('GET', URL, true);
            req.onload = function () {
            if (req.status === 200) { 
                    resolve(req.responseText);   // pending 状态 变成 fulfilled(成功)
                } else {
                    reject(new Error(req.statusText)); // pending 状态 变成 rejected(失败)
                } 
            };
            req.onerror = function () {
                reject(new Error(req.statusText));
            };
            req.send(); 
        });
    }

感悟:Promise就是为了解决异步调用的而设计的。只要是异步的调用,通过new Promise()处理,就可以把异步的处理程序放到 then里面处理,解决回调地狱的问题。

2、promise 内部属性介绍:

   注意: js中内部属性 使用 双 方括号 来表示的双方括号代表这是JavaScript引擎内部使用的属性/方法,可以帮助debug。但是正常JavaScript代码是取不到这些属性的。

  • promise 对象状态属性:  [[PromiseState]]
    • 状态变化:只有 2种 变化。
      pending ==> fulfilled    或
      pending ==> rejected
    • 如何修改对象的状态:只有 下面3 种方法 可以改变 对象状态。
      使用 构造函数提供的 resolvereject 方法;另外 通过 throw 抛出一个错误 也会改变 对象状态。 
      (1)  resolve(value):如果当前是 pending 就会变成 resolved
      (2)  reject(reason):如果当前是 pending 就会变为  rejected
      (3)  抛出异常:如果当前是 pending 就会变成 rejected

  • promise 对象结果值属性:[[PromiseResult]]

3、Promise.resolve()  :将现有对象转为 Promise 对象的快捷方式。(注意,这个不是Promise实例的方法,不要混为一谈。即 new PromisePromise.resolve() 都是创建Promise 对象的方法)

   Promise.resolve() 的参数 分 4 种情况:https://blog.csdn.net/ixygj197875/article/details/79183843

  1. 不带有任何参数,直接返回一个resolved状态的 Promise 对象。
        const p = Promise.resolve()
    
        p.then(res => {
          console.log(res) // undefined
        })
  2. 传一个普通的对象,将普通对象 转为 Promise 对象
    let p1 =Promise.resolve({name:'xixi',age:'xxxx'});
    
    p1.then(result => {
        console.log(result);  // {name: "xixi", age: "xxxx"}
    });
  3. 传一个 Promise 对象实例, Promise.resolve将不做任何修改、原封不动地返回这个实例。
        let p = new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve('success')
          }, 500)
        })
    
        let pp = Promise.resolve(p)
    
        pp.then(result => {
          console.log(result) // success
        })
    
        console.log(pp === p) // true
  4. 传一个 thenable对象(thenable对象指的是具有then方法的对象),Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。(一般不会使用这种情况的参数,这里不详细说明)
        let thenable = {
          then: function (resolve, reject) {
            resolve(42) // 这里 then函数返回的不是一个 Promise对象就不会执行下面那个then里面的函数
          }
        }
        let p1 = Promise.resolve(thenable)
        p1.then(function (value) {
          console.log(value) // 42
        }) 

感悟:Promise.resolve() 只是把数据 变成 promise对象,可以使用then拆分结构而已。即Promise.resolve() 并不是用来处理异步接口的。这点和 new Promise是又很大不一样的。

4、Promise.reject()  :快速的获取一个拒绝状态的 Promise 对象。(这个方法 和 Promise.resolve() 方法是一样功能。Promise 对象会有两个状态变化,Promise.reject()创建的对象就是执行catch中的函数)

   注意:Promise.reject() 后面没有 catch 函数就会报错的。Promise.resolve() 后面没有 catch函数是 不会报错的。

5、Promise.resolve().then()  的 执行顺序比 setTimeout(fn, 0) 先。因为微任务比宏任务先执行。

setTimeout(function () {
  console.log('three');
}, 0);
Promise.resolve().then(
function () {   console.log('two'); });
console.log(
'one'); // one // two // three

 

6、promise.then() 方法 返回一个新的promise对象。

  • promise.then() 返回 的新 promise 的结果状态由上面决定?
    (1)简单表达:由then()指定的回调函数执行结果决定。
    (2)详细表达:
             ①  如果抛出异常,新promise变为 rejected, reason 为抛出的异常。
             ②  如果返回的是  非 promise 的任意值,新 promise 变为 resolved,value 为返回的值。(没有返回值,就是返回undefined)
             ③  如果返回的是   另一个新 promise,此 promise 的结果就会成为 新 promise 的结果。
                   说明下:函数内部的异步程序,是无法直接决定函数本身返回值的。所以  新的 promise对象 结果状态想要 由  异步函数决定,只能把这个异步函数 变成 promise对象。则 promise.then()的结果就是就是内部这个promise的结果。

  • promise对象then方法中,return 值 和return Promise.resolve('值') 的区别:

      总结:他们其实是一样的,因为 then方法里面   “return 值”,最后也会包装成 return Promise.resolve('值') 返回的。

7、Promise.reject() 对象 有多个 then 函数,只有一个catch 函数(任意位置),链式函数会先执行 catch 里面的函数,并且从这个 catch 开始继续执行下面的链式写法的函数。(Promise.resolve()同样的道理,从第一个then开始连续问下执行)

    Promise.reject('resolve')
    .then(res => {  console.log('sucess1', res) })
    .then(res => {  console.log('sucess2', res) })
    .catch(() => {  console.log('fail_1')  // 没有显示写返回,默认返回的就是 Promise.resolve()
    })
    .then(res => {
      console.log('sucess5', res)
      return Promise.reject('resolve')
    })
    .then(res => {  console.log('sucess6', res) })
    .catch(() => {  console.log('fail_2')  })
    
    // fail_1
    // sucess5 undefined
    // fail_2

 8、如何中断 promise 链:

  • 当使用promise的then链式调用时,在中间中断,不再调用后面的回调函数。
  • 办法:在回调函数照顾你返回一个pending状态的promise对象。
    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('OK')
        }, 1000)
    }).then(value => {
        console.log(1)
        //中断 promise 链,有且只有一个方式
        return new Promise(() => {})
    }).then(value => {
        console.log(2)
    }).then(value => {
        console.log(3)
    }).catch(reason => {
        console.warn(reason)
    })

    个人有一个疑问:promise一直处于 pending状态,是不是就无法被回收,从而引起内存泄漏。
    不过这种中断是没有意义的。https://www.zhihu.com/question/495412354/answer/2245116174

 

9、Promise.all 和 Promise.race :https://www.jianshu.com/p/7e60fc1be1b2

  • Promise.all 可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
  • Promse.race 顾名思义,就是赛跑的意思。意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。【基本没有使用的场景】

 


 

 Async-Await https://www.cnblogs.com/SamWeb/p/8417940.html

  1. async用于申明一个function是异步的。而 await 可以认为是async wait的简写,等待一个异步方法执行完成。(Async-Await 是 基于 Promise的)
  2. async 函数 一定会有一个 promise 对象的返回值,如果 没有return 或 return 一个普通值,程序会把他包装成一个 promise 对象
        async function timeout () {
          return 'hello world'
        }
        console.log(timeout()) // Promise {<resolved>: "hello world"}
        console.log('虽然在后面,但是我先执行')
  3. await 只能在 async 定义的函数里面 使用,不能单独使用。实际上,await是强制把异步变成了同步。(await 后面可以放任何表达式,一般都是promise对象,因为放同步的表达式就没有 等待的意义了)
    特别注意:async 函数中有有一点很重要的特性,和 认知的不一样。就是 async 函数中有 aweit,程序进程虽然会暂停,但是他的返回值立即返回的,即 是同步的。返回的 promise对象的 状态 就需要等状态执行完。
    const promise = function(){
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('scuccee')
            },100000)
        })
    }
    
    async function test(){
        console.log("进入 1")
        const data = await promise()
        console.log("进入 2")
        return data
    }
    test()  // 立刻返回 一个 promis 对象,该对象 状态 会在 aweit执行完后,改变promise状态,且把 async函数的返回值 作为 promis对象的输出值。

    基于这个特性,Promise.all 和 .map 配合 可以非常方便的实现 并发请求:

    const userIds = [1, 2, 3];
    
    const promises = userIds.map(async (userId) => {
      const user = await fetchUserById(userId);
      return user.username;
    });
    
    const usernames = await Promise.all(promises);
    console.log(usernames);

    总结一句话: async 函数 返回值是同步的,返回一个pending状态的promise对象。只是状态的变化是 异步,需要函数执行完才能改变。

  4. async/await 的优势在于处理 then 链:https://www.jianshu.com/p/8a9bfc5128b4 
    个人见解:async/await 处理 嵌套请求真的很漂亮,没有回调地狱的困扰。
                 但是 await 必须结合 try / catch 使用,否则 无法 捕获 await 后面 promise对象的错误,async函数就会中断操作。
  5. await 函数 为什么 要 加 try catch:https://juejin.cn/post/7213362932423376933 
    a、await 后面的 promise 对象 状态是 reject时。如果不用 try catch 就需要加一个非空判断
  6. async 函数前后的执行顺序:  参考链接 
    a、 async 函数 和 普通函数一样,只有在执行到时 await 关键字时,才会让 async 函数后面的程序先执行。
    b、 async 函数 里面可以没有 await 关键字。这时 async 函数,就是一个普通函数。里面 有没有 异步 函数,和普通 函数中 有没有异步函数是一样的。
    async function foo() { 
    console.log(1);  // 和普通函数一样,执行到这里,下面碰到 await 关键字,才变成异步执行的程序
    await Promise.reject(3); 
    }
    
    foo().catch(console.log); 
    console.log(2);
    
    // 1
    // 2
    // 3

    个人体会 :1、Async-Await 使得 异步代码内的异步,可以同步执行。(简单理解,就是多个异步程序嵌套,只要通过 Async 告诉程序,最外围的一个函数是异步就可以了,里面的异步全部变成同步的执行。)
                这种 程序控制   在 某个请求 需要另一个请求返回的参数,这种多层请求嵌套的 时候 就非常有优势。
          2、promise 解决了回调函数不能 返回 数据的问题。因为promise是异步返回的数据不知道什么才有,所以基本没什么用。但是 await 的出现,使得 promise返回的数据可以进行使用了。(即异步回调函数的返回值可以控制什么时候使用了)
          3、Async-Await 相当于声明一个异步的程序空间,在这个空间中让异步的程序,同步执行。这样就不用再异步程序中嵌套回调函数了,全部都是同步的写法,并且增强是代码可读性。  https://www.cnblogs.com/yetianmao/p/8721884.html (这里的执行顺序,并不是想当然的那样,有时间在研究下


Generator 生成器:    https://www.kunjuke.com/jiaocheng/33366/  或  https://www.ruanyifeng.com/blog/2015/04/generator.html

1、yield / next 概念:https://www.jianshu.com/p/83da0901166f  或  https://blog.csdn.net/HHW223/article/details/106227524(推荐)

   yield:yield表达式 可以在生成器函数内部暂停代码的执行使其挂起。并且 yield 后面的 表达式  的值 会 返回给 调用它的 next() 函数。

   next :next()方法 启动生成器   以及 控制生成器的代码从暂停的位置开始继续往下执行。next 传入的参数,表示  上一个yield 表达式的返回值。

function* gen(x){
  var y = yield x + 2;  // yield 后面的表达式 值,作为g.next() 对象中value的值
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }     【这里的 传入的 参数2,作为 yield x+2  表达式的返回值】

2、Generator + Promise + 执行器  实际 开发实践:

posted @ 2019-03-17 21:37  吴飞ff  阅读(724)  评论(0编辑  收藏  举报