ES6笔记(7)-- Promise异步编程
系列文章 -- ES6笔记系列
很久很久以前,在做Node.js聊天室,使用MongoDB数据服务的时候就遇到了多重回调嵌套导致代码混乱的问题。
JS异步编程有利有弊,Promise的出现,改善了这一格局,让异步编程表现出类似“同步式代码”的形式,更好地体现了它的价值。
一、基本概念
1. Promises/A+规范
Promise是一种异步编程的解决方案,本质来说其实它是一种规范,Promises/A+规范
根据规范的定义,一个Promise对象应该至少有以下的基本特点
三个状态
Promise有三个状态:Pending(进行中)、Resolved或Fulfilled(已完成)、Rejected(已失败)
其中:Pending为Promise的初始状态;当Resolved成功时,会调用onFulfilled方法;当Rejected失败时,会调用onRejected方法
并且:状态只能从Pending转换为Resolved状态,或者从Pending转换为Rejected状态,不存在其他状态间的转换
Then方法
Promise必须提供一个then方法,用以访问当前值、最终的返回值以及失败的原因
最基本的then方法接受两个函数参数 promise.then(onFulfilled, onReject),对应于状态变化到Resolved和Rejected时的函数调用
2. Promise简单的实现
基于Promises/A+中规范的要求,可以自行实现一个基本的promise对象
可参考 一步一步实现一个Promise
二、基本使用
1. 使用相关插件
近年来,已经出现了很多Promise异步编程的插件,我们可以使用这些插件,常见的有:
例如使用jQuery新版Ajax模块内置的Deferred使用到了Promise,我们可以直接这样调用
// 回调函数的方式
$.get('url', function(rs) {
rs = JSON.parse(rs);
}); // Promise的形式
$.get('url').success(function(rs) {
rs = JSON.parse(rs);
})
不过jQuery中的Promise并不是完全按照Primises/A+规范来实现的,所以使用的时候可能会有问题,详见
2. 原生的Promise支持
ES6原生引入了Promise,它在很多现代浏览器上已经得到支持
在不支持原生Promise的环境下,除了可以直接使用一些第三方Promise库之外,还可以使用这个插件来兼容低版本浏览器
其实,ES6中的原生Promise实现与RSVP.js有很大的关系,所以相关语法也和它类似
比如在爬虫开发时,先获取用户资料,再获取该用户的文章,则可以用Promise,用类似以下的结构实现
function getUser(id) {
return new Promise(function(resolve, reject) {
$.get('/user?id=' + id, function(rs) {
rs = JSON.parse(rs);
if (rs.status !== 200) {
reject(rs);
} else {
resolve(rs);
}
});
});
}
function getContent(user) {
return new Promise(function(resolve, reject) {
$.get('/content', {
user: user
}, function(rs) {
rs = JSON.parse(rs);
if (rs.status !== 200) {
reject(rs);
} else {
resolve(rs);
}
});
});
}
getUser(11).then(function(rs) {
return getContent(rs.user);
}).catch(function(rs) {
throw Error(rs.msg);
}).then(function(rs) {
console.log(rs.content);
}).catch(function(rs) {
throw Error(rs.msg);
});
成功调用getUser之后,可以通过return 返回getContent(rs.user)这个promise对象,继续接下去的执行任务
除了直接返回这个新的promise对象,我们也可以直接返回一个数据,这个数据将会作为下一函数调用时的参数,且看例子:
function step(num) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (num > 0) {
resolve(num);
} else {
reject(0);
}
})
});
}
step(-1).then(function(num) {
console.log('resolve ' + num);
return -5;
}, function(num) {
console.log('reject ' + num); // reject 0
return 5;
}).then(step) // 下一个要执行的任务操作
.then(function(num) {
console.log('resolve ' + num); // resolve 5
}, function(num) {
console.log('reject ' + num);
});
当参数的数值为正数时,则直接resolve返回该数值,如果为负数则reject返回0,初始数值为-1,所以调用了reject
再看另一个例子:
function log(n) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (n % 2) {
resolve('奇数:' + n);
} else {
reject('偶数:' + n);
}
}, 1000);
});
}
log(1).then(function(data) {
console.log(data);
return log(2);
}, function(err) {
console.log(err);
return log(3);
}).then(function(data) {
console.log(data);
}, function(err) {
console.log(err);
});
以上代码执行之后

下面来详细介绍原生Promise的使用方法
new Promise(func)
通过实例化构造函数成一个promise对象,构造函数中有个函数参数,函数参数为(resolve, reject)的形式,供以函数内resolve成功以及reject失败的调用
.then(onFulfilled, onRejected)
then方法,方法带两个参数,可选,分别为成功时的回调以及失败时的回调
如上代码,log(1)时执行了resolve,log(2)时执行了reject
.catch(onRejected)
catch方法,方法带一个参数,为失败时的回调。其实.catch方法就是 .then(undefined, onRejected)的简化版,通过例子看看它的特点
function log(n) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (n % 2) {
resolve('奇数:' + n);
} else {
reject('偶数:' + n);
}
}, 1000);
});
}
log(2).then(function(data) {
console.log(data);
return log(3);
}).catch(function(err) {
console.log(err);
});
看这个例子,then中只有一个参数,调用log(2)之后reject执行,到达catch中输出

再看一个栗子,代码换成以下两种,输出都一样
log(1).then(function(data) {
console.log(data);
return log(2);
}).catch(function(err) {
console.log(err);
});
log(1).then(function(data) {
console.log(data);
return log(2);
}).then(undefined, function(err) {
console.log(err);
});
如此一来,第一轮log(1)的resolve后,自行调用log(2),从而执行reject,通过catch执行相应的输出

Promise.all()方法
Promise.all()方法接受一个promise的数组对象,只有数组中所有的promise都执行成功,整个promise才算成功,如果数组对象中有个promise执行失败,则整个promise就失败
看这个简单的例子,意图是调用log(1,2,3,4,5)这个promise完成之后再调用log(6),其中相应值小于3就resolve,反之就reject
function log() {
var promises = [];
[...arguments].forEach(function(n) {
promises.push(new Promise(function(resolve, reject) {
var info = '';
setTimeout(function() {
if (n < 3) {
info = '小于3 resolve:' + n;
console.log(info);
resolve(info);
} else {
info = 'reject:' + n;
console.log(info);
reject(info);
}
}, 1000);
}));
});
return Promise.all(promises);
}
log(1, 2, 3, 4, 5).then(function(data) {
console.log(data);
return log(6);
}).catch(function(err) {
console.log(err);
});
首先,依次将相应实例化的promise对象存入promises数组,通过Promise.all()调用返回,执行结果为

由输出结果知,1和2被resolve,3、4、5被reject,整个数组里已经有多于一个的promise对象被reject,仅仅触发了catch中的回调,所以log(6)得不到执行
Promise.race()方法
与Promise.all()类似,它也接受一个数组对象作为参数,但其意义不一样
只有数组中所有的promise都执行失败,整个promise才算失败,如果数组对象中有个promise执行成功,则整个promise就成功
把上述代码的all换成race,执行结果为:

由输出结果知,1和2被resolve,3、4、5被reject,整个数组里已经有多于一个的promise对象被resolve,触发了then中成功的回调,log(6)得到调用执行
因为这时还没有额外的then或catch方法来监视log(6)的状态,所以仅仅输出的在log函数中执行的结果
Promise.resolve()方法
除了在实例化Promise构造函数内部使用resolve之外,我们还可以直接调用resolve方法
var promise = Promise.resolve('resolve one');
// var promise = Promise.reject('reject one');
promise.then(function(data) {
console.log(data); // resolve one
}).catch(function(err) {
console.log(err);
});
参数除了可以直接指定值之外,还可以是一个Promise实例,具有then方法的对象,或者为空
参数为Promise实例,则将包装后返回该Promise实例
var promise = Promise.resolve($.get('url'));
前文说到jQuery的Promise实现方式并不是完全按照规范来着,通过Promise.resolve的包装,可以返回一个规范化的Promise实例
参数为空,则直接返回一个状态为resolved|fulfilled的Promise对象
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
直接resolve的Promise对象是在本轮事件循环结束时执行,setTimeout是在下一轮事件循环结束时执行,所以输出为:

参数为一个具有then方法的对象,则自动将这个对象转换为Promise对象并调用该then方法,如
Promise.resolve({
then: function(resolve, reject) {
resolve('resolved');
}
}).then(function(data) {
console.log(data); // resolved
}).catch(function(err) {
console.log(err);
});
Promise.reject()方法
除了在实例化Promise构造函数内部使用reject之外,我们还可以直接调用reject方法
类似于Promise.resolve()中参数的多样化,且看以下几个栗子:
Promise.resolve({
then: function(resolve, reject) {
reject('rejected');
}
}).then(function(data) {
console.log(data);
}).catch(function(err) {
console.log(err); // rejected
});
setTimeout(function () {
console.log('three');
}, 0);
Promise.reject().catch(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
var promise = Promise.reject($.get('url'));
// var promise = Promise.resolve('resolve one');
var promise = Promise.reject('reject one');
promise.then(function(data) {
console.log(data);
}).catch(function(err) {
console.log(err); // reject one
});
3. Promise的反模式
关于Promise有很多难点技巧点,比如以下四中调用方式的区别
doSomething().then(function () {
return doSomethingElse();
});
doSomethin().then(functiuoin () {
doSomethingElse();
});
doSomething().then(doSomethingElse());
doSomething().then(doSomethingElse);
相关解释见:谈谈使用 promise 时候的一些反模式
ES6笔记(7)-- Promise异步编程的更多相关文章
- 【ES6】Generator+Promise异步编程
一.概念 首先我们要理解Generator和Promise的概念. Generator:意思是生成器,可以在函数内部通过yeild来控制语句的执行或暂停状态. *Foo(){ yeild consol ...
- Promise异步编程解决方案
Promise是ES6中新增的异步编程解决方案,体现在代码中它是一个对象,可以通过 Promise 构造函数来实例化. 其最基本的使用 new Promise(function(resolve,rej ...
- ES6入门八:Promise异步编程与模拟实现源码
Promise的基本使用入门: ——实例化promise对象与注册回调 ——宏任务与微任务的执行顺序 ——then方法的链式调用与抛出错误(throw new Error) ——链式调用的返回值与传值 ...
- 【读书笔记】【深入理解ES6】#11-Promise与异步编程
异步编程的背景知识 JavaScript 引擎是基于单线程(Single-threaded)实际循环的概念构建的,同一时刻只允许一个代码块在执行. 所以需要跟踪即将运行的代码,那些代码被放在一个任务队 ...
- es6 generator函数的异步编程
es6 generator函数,我们都知道asycn和await是generator函数的语法糖,那么genertaor怎么样才能实现asycn和await的功能呢? 1.thunk函数 将函数 ...
- 学习Promise异步编程
JavaScript引擎建立在单线程事件循环的概念上.单线程( Single-threaded )意味着同一时刻只能执行一段代码.所以引擎无须留意那些"可能"运行的代码.代码会被放 ...
- promise异步编程的原理
一.起源 JavaScript中的异步由来已久,不论是定时函数,事件处理函数还是ajax异步加载都是异步编程的一种形式,我们现在以nodejs中异步读取文件为例来编写一个传统意义的异步函数: var ...
- async/await actor promise 异步编程
Python协程:从yield/send到async/await http://blog.guoyb.com/2016/07/03/python-coroutine/ Async/Await替代Pro ...
- Promise异步编程整理
1.单线程模型 单线程模型指的是,JavaScript 只在一个线程上运行.也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待. 注意,JavaScript 只在一个线 ...
随机推荐
- Java多线程系列--“JUC锁”05之 非公平锁
概要 前面两章分析了"公平锁的获取和释放机制",这一章开始对“非公平锁”的获取锁/释放锁的过程进行分析.内容包括:参考代码获取非公平锁(基于JDK1.7.0_40)释放非公平锁(基 ...
- SVNServer迁移
本文主要记录SVNServer从windows系列迁移到Centos的过程,以下几篇文章已经描述的很好了,本文就不再赘述. 注意问题: 配置权限时空格问题:包括前面不能有空格,=两边有空格等. 参考链 ...
- Entity Framework中IQueryable, IEnumerable, IList的区别
博客园里有这样的总结.这里姑且先列个题目, 提醒自己记忆.
- 将不确定变成确定~Uri文本文件不用浏览器自动打开,而是下载到本地
回到目录 这个标题有点长,简单来说就是,对于一个文件下载来说,是否可以提示用户,让它去保存,而不是将它在浏览器中打开,在浏览器中打开有个致命问题,那就是,如果你的页面编码和文件的编码不一致时,打开的就 ...
- Mybatis入门例子
Mybatis是轻量级的持久化框架,的确上手非常快. Mybatis大体上的思路就是由一个总的config文件配置全局的信息,比如mysql连接信息等.然后再mapper中指定查询的sql,以及参数和 ...
- 拥抱cnpm
在国内由于墙的原因,使用NPM安装模块经常会失败,要或在速度上会慢得跟蜗牛一样,这时候我们其实可以选择国内淘宝的NPM镜像,使用下面的命令来进行安装: npm install -g cnpm --re ...
- Atitti 知识图谱构建方法attilax 总结
Atitti 知识图谱构建方法attilax 总结 1.1. 知识图谱schema构建(体系化)1 1.2. 纵向垂直拓展(向上抽象,向下属性拓展)2 1.3. 横向拓展2 1.4. 网拓展2 1 ...
- C#学习系列-out与ref的区别
参考:http://www.microsoftvirtualacademy.com/Content/ViewContent.aspx?et=9851&m=9839&ct=31056 如 ...
- mysql 的简单优化
合理的建立索引的建议: (1) 越小的数据类型通常更好:越小的数据类型通常在磁盘.内存和CPU缓存中都需要更少的空间,处理起来更快. (2) 简单的数据类型更好:整型数据比起字符,处理开销更小,因 ...
- iOS-数据持久化基础-JSON与XML数据解析
解析的基本概念 所谓“解析”:从事先规定好的格式串中提取数据 解析的前提:提前约定好格式.数据提供方按照格式提供数据.数据获取方按照格式获取数据 iOS开发常见的解析:XML解析.JSON解析 一.X ...