ES6的异步操作之promise用法和async函数的具体使用


Posted in Javascript onDecember 06, 2019

promise 基本用法

Promise 对象是一个构造函数,用来生成 Promise 实例。Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。

resolve 函数的作用是,在异步操作成功时调用(Promise 对象的状态从 pending 变为 fulfilled),并将异步操作的结果,作为参数传递出去。

reject 函数的作用是,在异步操作失败时调用(Promise对象的状态从 pending 变为 rejected),并将异步操作报出的错误,作为参数传递出去。

const funPromise = function(options) {
 return new Promise(function(resolve, reject) {
  if (/* 异步操作成功 */){
   resolve(result);
  } else {
   reject(error);
  }
 });
}

resolve 函数的参数除了正常的值以外,还可能是另一个 Promise 实例,此时,初始 promise 的最终状态根据传入的新的 Promise 实例决定。

reject 方法的作用,相当于抛出错误。等同于 throw new Error('error')。

Promise.prototype.then()

Promise 实例具有 then 方法,它的作用是为 Promise 实例添加状态改变时的回调函数,即 Promise 实例生成以后,用 then 方法分别指定 fulfilled 状态和 rejected 状态的回调函数。

funPromise().then(function(result) {
 // fulfilled
}, function(error) {
 // rejected
})

then 方法可以接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 fulfilled 时调用,第二个回调函数是 Promise 对象的状态变为 rejected 时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受 Promise 对象传出的值作为参数。

then 方法返回的是一个新的 Promise 实例(注意,不是原来那个 Promise 实例)。因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法来处理上一个 then 方法中 return 的结果。

funPromise().then(function(result) {
 return result.data;
}).then(function(data) {
 // fulfilled
});

上面的代码使用 then 方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。并且,第一个 then 返回的结果也可以是另一个异步操作的 Promise 对象,这时后一个 then 函数,就会等待该 Promise 对象的状态发生变化,才会被调用。

funPromise().then(
 (result) => { return funPromise(result); }
).then(
 (data) => { /* fulfilled */ },
 (error) => { /* rejected */ }
);

上面代码中,第一个 then 方法指定的回调函数,返回的是另一个 Promise 对象。这时,第二个 then 方法指定的回调函数,就会等待这个新的 Promise 对象状态发生变化。如果变为 fulfilled,就调用第一个回调函数,如果状态变为 rejected,就调用第二个回调函数。

Promise.prototype.catch()

Promise 实例具有 catch 方法,它的作用是为 Promise 实例添加状态改变为 rejected 状态的回调函数,也就是 then 方法的第二个函数的替代写法。

funPromise().then(function(result) {
 // fulfilled
}).catch(function(error) {
 // 处理 funPromise 和之前 then 回调函数运行时发生的错误
});

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,无论前面有多少个 then 函数,其中的错误总是会被下一个 catch 语句捕获。

funPromise().then(function(result) {
 return funPromise(result);
}).then(function(data) {
 // fulfilled
}).catch(function(error) {
 // 处理前面三个 Promise 产生的错误
});

一般来说,不要在 then 方法里面定义 rejected 状态的回调函数(即 then 的第二个参数),总是使用 catch 方法,因为这种写法可以捕获前面 then 方法执行中的错误。

catch 方法返回的还是一个 Promise 对象,并且 catch 中如果没有抛出任何其它错误,那么该 Promise 对象则是 resolved 状态。而且后面还可以接着调用 then 方法,但是前面的 catch 不能捕获后面的 then 中的错误,所以尽量 catch 都写在最后。

Promise.all()

Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。其接受一个数组作为参数,数组中的值都是 Promise 实例,如果不是,就会先调用 Promise.resolve() 方法,将参数转为 Promise 实例,再进一步处理。

const p = Promise.all([funPromise(1), funPromise(2), funPromise(3)]);

p 的状态由数组中的值决定,分成两种情况。

  • 数组中 Primise 实例的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时数组中实例的返回值组成一个数组,传递给 p 的回调函数。
  • 只要数组的实例之中有一个被 rejected,p 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,也就是报错信息,会传递给 p 的回调函数。
p.then(function (results) {
 // 全部 fulfilled,results 是个数组,里面是每个实例的返回结果
}).catch(function(error){
 // 其中有一个变为 rejected
});

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

应用

用 Promise 对象实现 Ajax。

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

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

async / await 基本用法

当 async 函数执行的时候,一旦遇到 await 就会先等到 await 后的异步操作完成,再接着执行函数体内之后的语句。

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。

async function f() {
 return 'hello dora';
}

f().then(v => console.log(v))  // "hello dora"

async 函数内部抛出错误,会导致返回的 Promise 对象变为 rejected 状态。抛出的错误对象会被 catch 方法回调函数接收到。

async function f() {
 throw new Error('出错了');
}

f().catch( e => console.log(e)) // Error: 出错了

await 命令

正常情况下,await 命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

async function f() {
 return await 123;       // 等同于 return 123;
}

f().then(v => console.log(v)) // 123

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

任何一个 await 语句后面的 Promise 对象变为 rejected 状态,那么整个 async 函数就会中断执行。

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作,有两个解决办法:

第一种方法是可以将 await 放在 try...catch 结构里面,这样不管这个异步操作是否成功,后面的代码都会执行。

async function f() {
 try {
  await Promise.reject('出错了');
 } catch(e) { }
 return await Promise.resolve('hello dora');
}

f().then(v => console.log(v))  // hello dora

另一种方法是 await 后面的 Promise 对象再跟一个 catch 方法,处理前面可能出现的错误。

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

f().then(v => console.log(v))
// 出错了
// hello dora

使用注意点

1. 错误处理

前面已经说过,await 命令后面的 Promise 对象,运行结果可能是 rejected,所以防止出错的方法,就是最好把 await 命令放在 try...catch 代码块中。如果有多个 await 命令,可以统一放在 try...catch 结构中,如果只有一个 await,可以使用上例中的 catch 捕获 await 后面的 promise 抛出的错误。

const superagent = require('superagent');
const NUM_RETRIES = 3;

async function test() {
 let i;
 for (i = 0; i < NUM_RETRIES; i++) {
  try {
   await superagent.get('/api/xxx');
   break;
  } catch(err) {}
 }
}

test();

上面代码中,使用 try...catch 结构,实现多次重复尝试。如果 await 操作成功,就会使用 break 语句退出循环;如果失败,会被 catch 语句捕捉,然后进入下一轮循环。

2. 多个 await 异步操作并发执行

多个 await 命令后面的异步操作,如果不存在继发关系(即互不依赖),最好让它们同时触发,以缩短程序的执行时间。

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

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

3. forEach 等数组遍历方法的参数为 async 函数时是并发执行的

只有 async 函数内部是继发执行,外部不受影响,因此 forEach()、map() 等数组遍历方法的参数改成 async 时是并发执行的。

function dbFuc() { //这里不需要 async
 let docs = [{}, {}, {}];

 // 会得到错误结果
 docs.forEach(async (doc)=> {
  await funPromise(doc);
 });
}

上面代码会得到错误结果,原因是这时三个 funPromise(doc) 操作是并发执行的,也就是同时执行,而不是继发执行。因此正确的写法是采用 for 循环。

async function dbFuc() {
 let docs = [{}, {}, {}];

 for (let doc of docs) {
  await funPromise(doc);
 }
}

如果需要并发执行,可使用 Promise.all() 方法。

async function dbFuc() {
 let docs = [{}, {}, {}];
 let promises = docs.map((doc) => funPromise(doc));

 let results = await Promise.all(promises);
 return results;
}

有一组异步操作,需要按照顺序完成。

async function logInOrder(urls) {
 // 并发读取远程URL
 const textPromises = urls.map(async url => {
  const response = await fetch(url);
  return response.text();
 });

 // 按次序输出
 for (const textPromise of textPromises) {
  console.log(await textPromise);
 }
}

上面代码中,虽然 map 方法的参数是 async 函数,但它是并发执行的,因为只有 async 函数内部是继发执行,外部不受影响。后面的 for..of 循环内部使用了 await,因此实现了按顺序输出。

参考链接:
Promise 对象
async 函数

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript正则表达式中参数g(全局)的作用
Nov 11 Javascript
JavaScript设计模式之适配器模式介绍
Dec 28 Javascript
使用VS开发 Node.js指南
Jan 06 Javascript
iframe中子父类窗口调用JS的方法及注意事项
Aug 25 Javascript
jQuery EasyUI中的日期控件DateBox修改方法
Nov 09 Javascript
微信小程序 表单Form实例详解(附源码)
Dec 22 Javascript
js中bool值的转换及“&amp;&amp;”、“||”、 “!!”详解
Dec 21 Javascript
原生JS实现网页手机音乐播放器 歌词同步播放的示例
Feb 02 Javascript
在Vue中使用axios请求拦截的实现方法
Oct 25 Javascript
JS获取当前时间的实例代码(昨天、今天、明天)
Nov 13 Javascript
laydate只显示时分 不显示秒的功能实现方法
Sep 28 Javascript
游戏开发中如何使用CocosCreator进行音效处理
Apr 14 Javascript
原生JavaScript实现滑动拖动验证的示例代码
Dec 06 #Javascript
微信小程序 自定义弹窗实现过程(附代码)
Dec 05 #Javascript
Nuxt v-bind绑定img src不显示的解决
Dec 05 #Javascript
微信小程序swiper左右扩展各显示一半代码实例
Dec 05 #Javascript
微信小程序顶部导航栏可滑动并选中放大
Dec 05 #Javascript
微信小程序实现点击按钮后修改颜色
Dec 05 #Javascript
React 父子组件通信的实现方法
Dec 05 #Javascript
You might like
php 删除一个数组中的某个值.兼容多维数组!
2012/02/18 PHP
PHP闭包函数详解
2016/02/13 PHP
PHP文件上传类实例详解
2016/04/08 PHP
PHP面向对象程序设计之对象生成方法详解
2016/12/02 PHP
Laravel实现批量更新多条数据
2020/04/06 PHP
jQuery插件开发全解析
2012/10/10 Javascript
jquery miniui 教程 表格控件 合并单元格应用
2012/11/25 Javascript
js onload处理html页面加载之后的事件
2013/10/30 Javascript
javascript中动态加载js文件多种解决办法总结
2013/11/15 Javascript
JS对象转换为Jquery对象示例
2014/01/26 Javascript
jQuery中unwrap()方法用法实例
2015/01/16 Javascript
JavaScript编写推箱子游戏
2015/07/07 Javascript
Bootstrap打造一个左侧折叠菜单的系统模板(一)
2016/05/17 Javascript
JavaScript中原型链存在的问题解析
2016/09/25 Javascript
原生javascript实现分页效果
2017/04/21 Javascript
原生JS实现ajax与ajax的跨域请求实例
2017/12/01 Javascript
Vue中的$set的使用实例代码
2018/10/08 Javascript
JavaScript 格式化数字、金额、千分位、保留几位小数、舍入舍去
2019/07/23 Javascript
原生js实现日历效果
2020/03/02 Javascript
Node.js中文件系统fs模块的使用及常用接口
2020/03/06 Javascript
jQuery zTree如何改变指定节点文本样式
2020/10/16 jQuery
vue组件中节流函数的失效的原因和解决方法
2020/12/02 Vue.js
举例讲解Python的Tornado框架实现数据可视化的教程
2015/05/02 Python
详解Python中open()函数指定文件打开方式的用法
2016/06/04 Python
python 输出上个月的月末日期实例
2018/04/11 Python
Python中多个数组行合并及列合并的方法总结
2018/04/12 Python
Python + selenium自动化环境搭建的完整步骤
2018/05/19 Python
Python实现矩阵相乘的三种方法小结
2018/07/26 Python
通过python爬虫赚钱的方法
2019/01/29 Python
PyTorch安装与基本使用详解
2020/08/31 Python
用HTML5制作烟火效果的教程
2015/05/12 HTML / CSS
使用HTML5 Canvas API控制字体的显示与渲染的方法
2016/03/24 HTML / CSS
英语商务邀请函范文
2014/01/16 职场文书
网上蛋糕店创业计划书
2014/01/24 职场文书
环境卫生整治简报
2015/07/20 职场文书
2016年师德师风学习心得体会
2016/01/12 职场文书