Promise 和 Async / Await 的介绍

发布于 2021-12-07 12:50:06 字数 10143 浏览 1288 评论 0

1. Promise 是为了解决什么问题?

解决了回调地狱 Callback Hell 的问题。

回调地狱的问题并不只是在于缩进太多(如下图),至少在阅读如下代码的时候不会有什么障碍。

1. 难于理解

真正的问题在于逻辑难于理解,如下图代码中,我们假定形如 doSomething 的调用又涉及到一步调用,那么阅读的人可能会需要把调用顺序记在脑袋里才行。

listen( "click", function handler(evt){ 
  doSomething1();
  doSomething2();
  doSomething3();
  doSomething4();
  setTimeout( function request(){ 
    doSomething8();
    doSomething9();
    doSomething10();
    ajax( "http:// some. url. 1", function response( text){ 
      if (text == "hello") { 
        handler(); 
      } else if (text == "world") { 
        request(); 
      } 
    }); 
    doSomething11();
    doSomething12();
    doSomething13();
  }, 500); 
  doSomething5();
  doSomething6();
  doSomething7();
});

相比较起来,如下的一个使用 Promise 包装的例子就很简洁,其关键点在于 Promise 的构造函数中,只负责成功/失败的通知,而后续的操作放在了 then 中

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});

2. 信任问题

经过 Promise 流程的调用,将同步调用放在 then 中就不会出现同步调用意外的早于异步调用的情况;而且 Promise 的结果不能被篡改,多次调用的回调结果都能保持一致。

2. 如何模拟实现 Promise

let status = 'pending';
class Promise {
  constructor(func){
    func(this._resolve.bind(this), this._reject.bind(this))
  }
  _resolve() {
    status = 'fullfilled';
  }
  _reject() {
    status = 'rejected';
  }
  then(succCallback, failCallback) {
    if(status == 'pending') {
      this.succCbList.push(succCallback);
      this.failCbList.push(failCallback);
    } else if(status== 'fullfilled'){
      succCbList.forEach(f => {f();})
    } else {
      failCallback.forEach(f => {f();})
    }
  }
}

3. 实际的应用场景


service.interceptors.response.use(
  response => {
    const res = response.data;
    if (res.status == 10101 || res.status == 401) {
      G.U.clearCookie()
      G.U.removeCookie('token', document.domain)
      G.U.removeCookie('token', G.U.getTopDomain())
      Message.error({
        message: res.message || '未授权用户,即将跳转到登录界面',
        duration: 2 * 1000
      })
      setTimeout(() => {
        window.location.href = '/login.html'
      }, 2000)
    } else if (res.status == 404) {
      Message.error({
        message: res.message || '请求找不到',
        duration: 5 * 1000
      })
    } else if (res.status == 500 || res.status == 503) {
      Message.error({
        message: res.message || '服务器错误',
        duration: 5 * 1000
      })
    } else if (G.U.isBlob(res)) {
      // do nothing
    } else if (res.status != 0) {
      Message.error({
        message: res.message || res.errorMessage || '未知异常',
        duration: 5 * 1000
      })
    }
    return response.data
  },
  error => {
    Message.error({
      message: error.message,
      duration: 5 * 1000
    })
  }
)
service.interceptors.response.use(
  response => {
    const res = response.data;
    let message;

    return new Promise((resolve, reject) => {
      if (res.status == 10101 || res.status == 401) {
        G.U.clearCookie()
        G.U.removeCookie('token', document.domain)
        G.U.removeCookie('token', G.U.getTopDomain())
        setTimeout(() => { window.location.href = '/login.html' }, 2000)
        message = res.message || '未授权用户,即将跳转到登录界面';
      } else if (res.status == 404) {
        message = res.message || '请求找不到';
      } else if (res.status == 500 || res.status == 503) {
        message = res.message || '服务器错误';
      } else if (G.U.isBlob(res)) {
        // do nothing
      } else if (res.status != 0) {
        message = res.message || res.errorMessage || '未知异常'
      }

      if(message != undefined) {
        reject(message);
        Message.error({ message, duration: 3 * 1000 })
      } else {
        resolve(response.data);
      }
    });
  },
  error => {
    Message.error({ message: error.message, duration: 5 * 1000 })
    return Promise.reject(error.message)
  }
)

返回的 Promise 对象,可以让调用的位置,按照 Promise 的缘分书写 then/catch。下面是来自 components/activity/view.vue 使用 Promise 改写后的代码,后者看看起来更简洁:

methods: {
  queryAuditStatus(id){
    G.R.Common.getAuditProgress('ACTIVITY_AUDIT', id).then(resp => {
      if(resp.status == 0) {
        this.processData = resp.data;
      }
    })
  }
},
mounted() {
  G.R.Product.getActivityDetail(params.id).then(resp => {    
    if(resp.status == 0 && resp.data) {
      this.product = Activity.parse(resp.data);
      this.queryAuditStatus(params.id);
    } else{
      this.$message({
        type:'info',
        message:resp.message
      })
    }
    this.loading = false;
  })
}
let id = G.U.getParam('id', this);

this.loading = true;

G.R.Product.getActivityDetail(id).then(resp => {
  this.product = Activity.parse(resp.data || {});
  return G.R.Common.getAuditProgress('ACTIVITY_AUDIT', id);
}).then(resp => {
  this.processData = resp.data;
}).finally(() => {
  this.loading = false;
});

4.Async/Await 的含义和基本用法

1. Generator

可以把 Generator 函数看成是一个状态机,封装了多个内部状态,执行 Generator 函数会返回一个遍历器对象,代表 Generator 函数的内部指针。虽然 Generator 函数是一个普通函数,但是有两个特征:

  • function 关键字与函数名之间有一个星号;
  • 函数体内部使用 yield 表达式,定义不同的内部状态。
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

Async/Await

async 就是 Generator 函数的语法糖。如下是使用 Generator 函数依次读取两个文件的代码:

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

使用 async 函数改写后如下,语法上看只是简单地把 * 换成 async,yield 替换成 await 而已。

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

但 async 对 Generator 函数的改进体现在如下四点:

  • Generator 必须依靠内置执行器(co 模块)通过 next 语法执行,而 async 函数自带执行器执行起来像普通函数
  • 更好的语义,比起 * 和 yield,使用 async 和 await 语义更清楚
  • 更广的适用性,yield 命令后面只能是T runk 函数和 Promise 对象,而 async 的 await 后面可以是 Promisee 对象和原始值
  • async 函数的返回值是 Promise 对象,比起 Generator 函数的返回值是 Iteretor 方便,可以使用 then 方法指定下一步操作

5. Async/Await 的实际应用

未经过改写的代码

methods: {
  handlePreview(file) {
    this.dialogImageUrl = file.url;
    this.dialogVisible = true;
  },
  queryAuditStatus(id){
    G.R.Common.getAuditProgress('ACTIVITY_AUDIT', id).then(resp => {
      if(resp.status == 0) {
        this.processData = resp.data;
      }
    })
  },
  lookProduct(id){
    this.$router.push({name:'prod_product_manage_view',params:{id:id}})
  }
},
mounted() {
  let params = this.$route.params || {};
  this.loading = true;
  G.R.Product.getActivityDetail(params.id).then(resp => {
    console.log(resp)
    if(resp.status == 0 && resp.data) {
      this.product = Activity.parse(resp.data);
      this.queryAuditStatus(params.id);
    }else{
      this.$message({
        type:'info',
        message:resp.message
      })
    }
    this.loading = false;
  })
  let id = {
    id:params.id
  }
  G.R.Product.getActivityOtherDispose(id).then(resp => {
    console.log(21321321312312)
    console.log(resp)
    if(resp.status == 0 && resp.data) {
      if(resp.data.length){
        this.activityOther = resp.data;
      }
    }
    this.loading = false;
  })
}

使用 await 该改写后的代码:

  • 代码更少,可读性更好
  • 在 loading 的控制上更直接
  # /components/activity/view.vue

  async mounted() {
    let params = this.$route.params || {};
    let id = params.id;
    let activityRes = await G.R.Product.getActivityDetail(id);
    let processRes = await G.R.Common.getAuditProgress('ACTIVITY_AUDIT', id);
    let otherRes = await G.R.Product.getActivityOtherDispose({id});

    this.loading = true;

    if(activityRes.ok && processRes.ok && otherRes.ok) {
      this.product = Activity.parse(activityRes.data);
      this.processData = processRes.data;
      if(otherRes.data && otherRes.data.length) {
        this.activityOther = otherRes.data;
      }
    }

    this.loading = false;
  }

将三个请求改为并行缩短请求时间

  # /components/activity/view.vue
  async mounted() {
    ...
    let [activityRes, processRes, otherRes] = await Promise.all([
      G.R.Product.getActivityDetail(id),
      G.R.Common.getAuditProgress('ACTIVITY_AUDIT', id),
      G.R.Product.getActivityOtherDispose({id})
    ])
    ...
  }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
495 人气
更多

推荐作者

lorenzathorton8

文章 0 评论 0

Zero

文章 0 评论 0

萧瑟寒风

文章 0 评论 0

mylayout

文章 0 评论 0

tkewei

文章 0 评论 0

17818769742

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文