promise是什么
借用ECMAScript 6入门中对其的解释: Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise
对象。
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
谁在使用promise
在es6尚未提供原生的Promise之前,一些库入Q blueBird已经实现了Promimse达到的效果,解决异步编程的问题。也可以自己实现promise,但是要符合promiseA+规范
为什么使用promise
Promise的作用
解决了 “回调地狱”问题 解决了 异步执行结果无法同步获取 既然说Promise解决了这两个痛点,那么就先看一下Promise之前这两个问题是如何实现的。
回调地狱
回调地狱:常见的ajax、事件监听,在node中文件读取都属于异步编程,回调函数作为参数存在于这一异步操作的函数中,条件满足的时候会在异步操作中触发回调函数的执行。实际使用中回调可能存在多层嵌套,外部的回调依赖内部回调执行完成的结果,层层依赖,代码可读性降低。例如:
let fs = require('fs');
fs.readFile('1.txt','utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
console.log(data);
});
});
});
复制代码
同步异步结果
同步异步结果:很多情况同时发送多个异步请求,但是只有结果全部返回才继续执行。之前的实现方案有两种。1)制定计数器2)发布订阅
制定计数器
方法:制定计数器,每次调用次数递增,发现全部回调完成,输出结果。
function after (times,callback){
return function(){
times--;
if(times == 0){
callback();
}
}
}
let fn = after(3,function(){
console.log('调用了三次 才执行的方法')
})
fn();
fn();
fn();// 调用三次才执行的方法
复制代码
发布订阅
方法:先把结果暂存(订阅),满足条件时一次执行(发布)
let fs = require('fs');
let event = {
arr:[],
result:[],
on(fn){
this.arr.push(fn);
},
emit(data){
this.result.push(data);
this.arr.forEach(fn=>fn(this.result));
}
}
event.on(function(data){
if(data.length === 2){
console.log(data); // 最终的结果
}
});
fs.readFile('1.txt','utf8',function(err,data){
event.emit(data);
})
fs.readFile('2.txt','utf8',function(err,data){
event.emit(data);
});
复制代码
promis的基本用法
了解了以上,我们开始Promise
promise共有三种状态 等待态(pending)成功态(resolved)失败态(rejected) 状态的转化 pending -> resolved pending -> rejected resolved和rejected不能相互转化,状态只会更改一次
我们写一个简单的Promise
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});
复制代码
解析:Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“等待”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“等待”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
说了痛点,说了好的解决方案,那就用好的解决方案实现一下。
解决“回调地狱”
let fs = require('fs');
function read(filePath,encoding){
return new Promise((resolve,reject)=>{
fs.readFile(filePath,encoding,(err,data)=>{
if(err) reject(err);
resolve(data);
});
})
}
read('1.txt','utf8').then(data=>{
return read(data,'utf8')
}).then((data)=>{
return read(data,'utf8');
}).then((data)=>{
return read(data,'utf8');
}).then((data)=>{
console.log(data);
})
复制代码
解决同步异步结果
let fs = require('fs');
function read(filePath, encoding) {
return new Promise(function (resolve, reject) {
fs.readFile(filePath, encoding, function (err, data) {
if(err) reject(err);
resolve(data);
})
});
}
Promise.all([read('1.txt', 'utf8'), read('2.txt', 'utf8')]).then(data => {
console.log(data);
})
复制代码
从刚刚的例子中我们看到promise对代码逻辑做了简化。先从个api入手了解一下promise的功能。
Promise.prototype.then()
每个promise的实例 都有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。 then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例
)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
unction timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve('done');
}, ms)
});
}
timeout(100).then((value) => {
console.log(value);
Promise.reject('no')
}).then(null,(err)=>{
console.log(err)
}).catch((err)=>{
console.log(err);
})
// 先打印done 后进入到下个then的err环节里
复制代码
像上例链式的调用then,第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。
// 写法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 写法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
复制代码
Promise.all()
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。是类上的方法,属于promise的语法糖
const p = Promise.all([p1, p2, p3]);
复制代码
上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 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的回调函数。
之前解决同步异步结果就是使用promise.all()在这里不赘述了。
Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
复制代码
Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
复制代码
总结
本文只作为初学者的参考,了解promise的来由及基本的用法。本人也仅仅是个初学者,有问题可以一起讨论和补充。promise的各个api的深入使用和源码实现,后续深入了解后继续补充