zoukankan      html  css  js  c++  java
  • ES系列之Promise async 和 await

    概述

    promise是异步编程的一种解决方案,比传统的解决方案—回调函数和事件—更合理更强大。

    所谓的promise就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作的结果)。

    Promise是一个对象,从它这里可以获取异步操作的信息, Promise提供统一的API,各种异步操作都可以用同样的方法处理。

    promise的特点:

    1. 对象的状态不受外界影响,promise对象代表一个异步操作,有三种状态:pending(进行中)Fulfilled(已成功)Rejected(已失败)。只有异步操作的结果才可以决定当前是哪一种操作状态,任何其他操作都无法改变这种状态。

    2. 一旦状态改变就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变只有两种可能:从pending(进行中)变为Fulfilled(已成功)、或者从pending(进行中)变成Rejected(已失败)。状态发生改变就凝固了,不会再变,而是一直保持这个结果,这是就称为Resolved(已定型)。就算改变已经发生,再对Promise对象添加回调函数,也会立即得到这个结果,这个与event完全不同,事件的特点是,如果错过了它,再去监听是得不到结果的。

      有了promise对象,就可以将异步操作以同步操作表示出来,避免了层层嵌套的回调函数

      Promise的缺点:

      1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消。

      2. 如果不设置回调函数,Promise内部抛出错误不会反应到外部。

      3. 当处于Pending状态时,无法得知目前进展到哪一个阶段了。

    基本用法

       let promise = new Promise(function(res, rej) {
           // your code...
       })
       
       promise.then(function(value) {
           // res
       }, function(error) {
           // rej
       })

    执行顺序

    let promise = new Promise(function(resolve, reject) {
      // resolve, reject 才是真正的异步
      console.log('1')
      resolve()
    })
    
    promise.then(() => {
      console.log('2')
    })
    
    console.log('3')
    
    // 1 3 2

    异步加载图片

    // 异步加载图片
    function loadImageAsync(url) {
      return new Promise(function(resolve, reject) {
        let image = new Image()
    
        iamge.onload = function () {
          resolve(image)
        }
    
        image.onerror = function() {
          reject(new Error('不能加载' + url))
        }
    
        iamge.src = url
      })
    }

    模拟AJAX

    // promise对象实现AJAX
    // getJSON 是对XMLHttpReqest对象的封装,用于发出一个JSON数据的HTTP请求
    // 并返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数
    // 都带有参数
    
    let getJSON = function(url) {
      let promise = new Promise(function(resolve, reject) {
        let client = new XMLHttpRequest()
        client.open('GET', url)
        client.onreadystatechange = handler
        client.responseType = "json"
        client,setRequestHeader('Accept','application/json')
        client.send()
    
        function handler() {
          if(this.readyState !== 4) {
            return
          }
          if(this.status === 200) {
            resolve(this.response)
          } else {
            reject(new Error(this.statusText))
          }
        }
      })
      return promise
    }
    
    getJSON('/posts.json').then(function(json) {
      console.log('Contents:' + json)
    },function(error) {
      console.log('error',error)
    })

    promise中的promise

    /**
     * 如果resolve 和 reject 函数都带有参数,那么这些参数会被传递到回调函数。
     * reject函数接收Error对象的实例,表示抛出的错误
     * reslove 函数参数除了正确的值,还有可能是一个Promise对象
     */
    
    
    let p1 = new Promise(function(resolve, reject) {
      // ...
    })
    
    let p2 = new Promise(function(resolve, reject) {
      // ...
      resolve(p1)
    })
    /**p1 和 p2 都是Promise的实例,但是p2的resolve方法将p1作为参数
     * 即一个异步操作的结果是返回另一个异步操作。
     * p1的状态传递给p2.p1的状态决定了p2的状态
     */
    let p1 = new Promise(function(resolve, reject) {
      setTimeout(() => reject(new Error('fail')), 3000)
    })
    
    let p2 = new Promise(function(resolve, reject) {
      setTimeout(() => resolve(p1), 1000)
    })
    
    p2.then(result => console.log(result)).catch(error => console.log(error))
    
    /**
     * p1 是 一个Promise,3s之后变成rejected。
     * p2 的状态在1s后改变,resolve方法返回的是p1
     */

    Promise.prototype.then()

    作用:为了Promise实例添加状态改变时的回调。

    promise.then(function(value) {
        // res
    }, function(error) {
        // rej
    })
    
    // then方法有两个参数,第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。
    // then返回的是一个新的Promise实例

    Promise.prototype.catch()

    Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误的回调函数。

    另外then方法指定的回调函数如果在运行中抛出错误,也会被catch捕获。

    一般来说,不要在then方法中定义Rejected状态的回调(即then的第二个参数),而应该总是使用catch方法。

    p2.then(result => console.log(result))
    .catch(error => console.log(error))
    // .catch 处理 p2 和前一个回调函数运行时发生的错误
    
    // ====> 等同于
    p2.then(result => console.log(result))
    .then(null, err => console.log(err))

    Promise.all()

    Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

    const p = Promise.all([p1, p2, p3]); 
    // Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法, 将参数转为 Promise 实例,再进一步处理。

    p的状态由p1、p2、p3决定,分成两种情况。

    1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

    2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

    const databasePromise = connectDatabase();
    
    const booksPromise = databasePromise.then(findAllBooks);
    
    const userPromise = databasePromise.then(getCurrentUser);
    
    // booksPromise和userPromise是两个异步操作,只有等到它们的结果都返回了,才会触发pickTopRecommentations这个回调函数。
    // 如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法
    Promise.all([booksPromise, userPromise]).then(([books, user]) => pickTopRecommentations(books, user));

    async函数

    ES7标准引入了async函数,使得异步操作变得简单,——async就是Generator的语法糖。

    用法

    async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,

    一旦遇上await就会先返回,等到异步操作完成,再接着执行函数体内后面

    async function getStockPriceByName(name) {
      const symbol = await getStockSymbol(name);
      const stockPrice = await getStockPrice(symbol);
      return stockPrice;
    }
    
    getStockPriceByName('goog').then(function (result) {
      console.log(result);
    });
    // 1、函数前面的async关键字表明该函数内部有异步操作。
    // 2、调用该函数会立即返回一个Promise对象

    指定多少毫秒输出一个值

    function timeout(ms) {
      return new Promise((resolve) => {
        setTimeout(resolve, ms);
      });
    }
    
    async function asyncPrint(value, ms) {
      // 一旦遇上await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
      await timeout(ms);
      console.log(value);
    }
    
    asyncPrint('hello world', 3000); // 3s之后返回 'hello world'
    
    /** ------------- 改写 ------------------ */
    async function timeout(ms) {
      await new Promise((resolve) => {
        setTimeout(resolve, ms);
      });
    }
    
    async function asyncPrint(value, ms) {
      await timeout(ms);
      console.log(value);
    }
    
    asyncPrint('hello world', 3000);

    语法

    async函数返回一个promise对象,async函数内部return语句返回的值,会成为then方法回调函数的参数。

    Promise 对象的状态变化

    async函数返回的 Promise对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。

    也就是说,只有async函数内部的所有异步操作执行完,才会执行then方法指定的回调函数。

    正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。

    async function f() {
      return await 123;
    }
    
    // await命令的参数是数值123,它被转成 Promise 对象,并立即resolve。
    
    f().then(v => console.log(v)) // 123

    await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。

    async function f() {
      await Promise.reject('出错了');
    }
    
    f().then(v => console.log(v))
    .catch(e => console.log(e)) // 出错了

    只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。

    async function f() {
      await Promise.reject('出错了');
      await Promise.resolve('hello world'); // 不会执行 
    }
    // 第二个await不会执行

    现在,需要在第一个异步操作失败,也不要中断后面的异步操作。

    这是可以将第一个await放在try...catch...结构里面,这样不管这个异步操作返回的结果是resolve还是reject都会执行之后的await操作。

    async function f() {
      try {
        await Promise.reject('出错了');
      } catch (e) {
        console.log(e);
      }
      return await Promise.resolve('hello world');
    }
    
    f().then(v => console.log(v)) // hello world

    还有一种方法,就是在第一个await之后使用catch接收失败的回调,处理前面出现的错误。

    async function f() {
      await Promise.reject('出错了').catch(e => console.log(e));
      return await Promise.resolve('hello world');
    }
    
    f().then(v => console.log(v)) // 出错了 // hello world

    错误处理

    如果await后面的异步操作出错了,那么等同于async函数返回的Promise对象被reject了。

    async function f() {
      await new Promise(function (resolve, reject) {
        throw new Error('出错了');
      });
    }
    
    f().then(v => console.log(v)).catch(e => console.log(e)) // Error:出错了
    // async函数f执行后,await后面的 Promise 对象会抛出一个错误对象,
    // 导致catch方法的回调函数被调用,它的参数就是抛出的错误对象。
    // 有多个await命令,则可以统一放在try...catch...结构中
    async function main() {
      try {
        const val1 = await firstStep();
        const val2 = await secondStep(val1);
        const val3 = await thirdStep(val1, val2);
    
        console.log('Final: ', val3);
      } catch (err) {
        console.error(err);
      }
    }

    await使用注意点

    第一点,前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以好把await命令放在try...catch代码块中。

    async function myFunction() {
      try {
        await somethingThatReturnsAPromise();
      } catch (err) {
        console.log(err);
      }
    }
    
    // 另一种写法 
    
    async function myFunction() {
      await somethingThatReturnsAPromise().catch(function (err) {
        console.log(err);
      });
    }

    第二点,多个await命令后面的异步操作,如果不存在继发关系,好让它们同时触发

    // 写法一 
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    
    // 写法二 
    let fooPromise = getFoo();
    let barPromise = getBar();
    let foo = await fooPromise;
    let bar = await barPromise;

    第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。

    async function dbFuc(db) {
      let docs = [{}, {}, {}];
      // 报错
      docs.forEach(function (doc) {
        await db.post(doc);
      });
    }
    
    // 如果将forEach方法的参数改成async函数,也有问题。
    function dbFuc(db) { //这里不需要 async
      let docs = [{}, {}, {}];
      // 可能得到错误结果
      docs.forEach(async function (doc) {
        await db.post(doc);
      });
    }
    // 原因是这时三个db.post操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。
    async function dbFuc(db) {
      let docs = [{}, {}, {}];
      for (let doc of docs) {
        await db.post(doc);
      }
    }
    // 如果确实希望多个请求并发执行,可以使用Promise.all方法。当三个请求都会resolved时,下面两种写法效果相同。
    
    async function dbFuc(db) {
      let docs = [{}, {}, {}];
      let promises = docs.map((doc) => db.post(doc));
    
      let results = await Promise.all(promises);
      console.log(results);
    }
    
    // 或者使用下面的写法 
    
    async function dbFuc(db) {
      let docs = [{}, {}, {}];
      let promises = docs.map((doc) => db.post(doc));
    
      let results = [];
      for (let promise of promises) {
        results.push(await promise);
      }
      console.log(results);
    }

    实例

     var pro = new Promise(function(){
          var a = 1;
    })
    console.log(pro); 

    读文件的方法

    const fs = require('fs')
    
    // 总结:只要 new 了一个具体的异步操作,这个异步操作被创建的一瞬间,就会立即执行;
    function readFileByPath(fpath) {
      const p = new Promise(function() {
        fs.readFile(fpath, 'utf-8', (err, result) => {
          if (err) return console.log('读文件失败:' + err.message)
          console.log(result)
        })
      })
    }
    
    readFileByPath('./files/3.txt') // 文件必须存在

    使用.then()进一步封装读文件的操作

    封装原则:不要在方法内部显示结果,要把结果返回给调用者,不要提调用者做决定!!

     使用.catch()进一步封装读文件的操作

    const fs = require('fs')
    
    function readFileByPath(fPath) {
      return new Promise(function(resolve,reject){
        fs.readFile(fPath, 'utf-8', (err, result) => {
          if (err) return reject(arr)
          resolve(result)
        })
      })
    }
    
    // 一般.then()方法中,失败的回调可以省略,但是省略以后读取文件失败时,无法接收结果
    // 这是,我们可以使用.catch() 来指定失败的回调
    /* --------------读一个-------------------- */
    readFileByPath('./files/1.txt')
      .then(function(result) {
        console.log(result)
      })
      .catch(err => console.log(err.message))
    
    /* ---------------读多个----------------------- */
    readFileByPath('./files/1.txt')
      .then(function(result){
        console.log(result)
        return readFileByPath('./files/2.txt')
      })
      .then(function(result){
        console.log(result)
        return readFileByPath('./files/3.txt')
      })
      .catch(err => console.log(err.message))

     async和await

    const fs = require('fs')
    
    function readFileByPath(fPath) {
      return new Promise(function(resolve,reject){
        fs.readFile(fPath, 'utf-8', (err, result) => {
          if (err) return reject(arr)
          resolve(result)
        })
      })
    }
    
    // async 用来修饰异步方法
    // await 只能用在被 async 修饰的方法中
    // 同时,await 是用来修饰 Promise 实例对象的;简化promise对象
    
    console.log("开始");
    async function readAll () {
      console.log("方法头部");
      const result1 = await readFileByPath('./files/1.txt')
      console.log(result1)
      const result2 = await readFileByPath('./files/2.txt')
      console.log(result2)
      const result3 = await readFileByPath('./files/3.txt')
      console.log(result3)
      console.log("方法尾部");
    }
    console.log("结束");
    
    readAll()

    运行顺序:

    原因:

    /* async修饰的 readAll()方法异步方法,在调用这个readAll()方法的时候,js主线程进入这个方法
    这时,还是主线程在执行,输出“方法头部”,然后在 await是异步操作,这是主线程就退出这个方法,
    执行“结束”,然后就是异步的方法顺序执行 */
  • 相关阅读:
    【Android】升级ADT 22 引发的错误
    【Android】使用SearchView时软键盘不支持actionSearch的问题
    Mac 升级 OS X 10.8.2 后 VirtualBox 无法启动的问题
    2012年总结
    【Andorid X 项目笔记】魅族照片剪切问题(4)
    【读书笔记】浪潮之巅
    ormlite更改数据库默认位置
    Android实用代码七段(二)
    Android实用代码七段(三)
    【Android】嵌套Fragment无法接收onCreateOptionsMenu事件的问题
  • 原文地址:https://www.cnblogs.com/houfee/p/9945240.html
Copyright © 2011-2022 走看看