Promise是抽象异步处理对象以及对其进行各类操做的组件,其实Promise就是一个对象,用来传递异步操做的消息,它不是某门语言特有的属性,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象,Promise对象有如下两个特色:node
1.对象的状态不受外界影响
2.一旦状态改变,就不会再变,任什么时候候均可以获得这个结果
git
Promise也如下缺点:es6
1.没法取消Promise,一旦新建它就会当即执行,没法中途取消。
2.若是不设置回调函数,Promise内部抛出的错误,不会反应到外部。
3.当处于Pending状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成)。
github
关于Promise的详细介绍和用法,能够参考JavaScript Promise迷你书npm
2.为何要在js中使用Promise
ES6新增了Promise这个特性的意义在于,以往在js中处理异步操做一般是使用回调函数和事件,而有了Promise对象,就能够将异步操做以同步操做的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操做更加容易。拿node.js读取文件举例子,基于JavaScript的异步处理,以往都是想下面这样利用回调函数:segmentfault
var fs = require('fs');
fs.readFile('demo.txt', 'utf8', function (err, data) {
if (err) throw err;
console.log(data);
});
复制代码
而使用Promise能够这样写:数组
var fs = require('fs');
function readFile(fileName) {
return new Promise(function(resolve, reject) {
fs.readFile(fileName, function (err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
readFile(('demo.txt').then(
function(data) {
console.log(data);
},
function(err) {
throw err;
}
);
复制代码
这样的结构就比较清晰了,有同窗看到这要问了,要是有多重嵌套怎么办,来看下面这个例子,假如咱们有多个延时任务要处理,在js中便使用setTimeout来实现,在以往就是js中每每是这样写:promise
var taskFun = function() {
setTimeout(function() {
// do timeoutTask1
console.log("do timeoutTask1");
setTimeout(function() {
// do timeoutTask2
console.log("do timeoutTask2");
setTimeout(function() {
// dotimeoutTask3
console.log("do timeoutTask3");
}, 3000);
}, 1000);
}, 2000);
}
taskFun();
复制代码
这样写嵌套了多层回调结构,若是业务逻辑再复杂一点,就会进入到所谓的回调地狱,那么若是用Promise能够这样来写:浏览器
new Promise(function(resolve, reject) {
console.log("start timeoutTask1");
setTimeout(resolve, 3000);
}).then(function() {
// do timeoutTask1
console.log("do timeoutTask1");
return new Promise(function(resolve, reject) {
console.log("start timeoutTask2");
setTimeout(resolve, 1000);
});
}).then(function() {
// do timeoutTask1
console.log("do timeoutTask2");
return new Promise(function(resolve, reject) {
console.log("start timeoutTask3");
setTimeout(resolve, 2000);
});
}).then(function() {
// do timeoutTask1
console.log("do timeoutTask3");
});
复制代码
咱们还能够用Promise这样写,把每一个任务提炼成单独函数,让代码看起来更加优雅直观:缓存
function timeoutTask1() {
return new Promise(function(resolve, reject) {
console.log("start timeoutTask1");
setTimeout(resolve, 3000);
});
}
function timeoutTask2() {
return new Promise(function(resolve, reject) {
console.log("start timeoutTask2");
setTimeout(resolve, 1000);
});
}
function timeoutTask3() {
return new Promise(function(resolve, reject) {
console.log("start timeoutTask3");
setTimeout(resolve, 2000);
});
}
timeoutTask1()
.then(function() {
// do timeoutTask1
console.log("do timeoutTask1");
})
.then(timeoutTask2)
.then(function() {
// do timeoutTask2
console.log("do timeoutTask2");
})
.then(timeoutTask3)
.then(function() {
// do timeoutTask2
console.log("do timeoutTask3");
});
复制代码
执行的顺序为:
Promise/A+是Promise的一个主流规范,浏览器,node和JS库依据此规范来实现相应的功能,以此规范来实现一个Promise也能够叫作实现一个Promise/A+。具体内容可参考Promise/A+规范
1.类和构造器的构建
Promise 的参数是一个函数 task,把内部定义 resolve 和reject方法做为参数传到 task中,调用 task。当异步操做成功后会调用 resolve 方法,而后就会执行 then 中注册的回调函数,失败是调用reject方法。
class Promise {
constructor(task) {
let self = this; //缓存this
self.status = 'pending'; //默认状态为pending
self.value = undefined; //存放着此promise的结果
self.onResolvedCallbacks = []; //存放着全部成功的回调函数
self.onRejectedCallbacks = []; //存放着全部的失败的回调函数
// 调用resolve方法能够把promise状态变成成功态
function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => { // 异步执行全部的回调函数
// 若是当前状态是初始态(pending),则转成成功态
// 此处这个写判断的缘由是由于resolved和rejected两个状态只能由pending转化而来,二者不能相互转化
if (self.status == 'pending') {
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(item => item(self.value));
}
});
}
// 调用reject方法能够把当前的promise状态变成失败态
function reject(value) {
setTimeout(() => {
if (self.status == 'pending') {
self.value = value;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(item => item(value));
}
});
}
// 当即执行传入的任务
try {
task(resolve, reject);
} catch (e) {
reject(e);
}
}
}
复制代码
代码思路与要点:
- self = this, 不用担忧this指向忽然改变问题。
- 每一个 Promise 存在三个互斥状态:pending、fulfilled、rejected。
- Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种状况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会当即获得这个结果。这与事件彻底不一样,事件的特色是,若是你错过了它,再去监听,是得不到结果的。
- 建立 Promise 对象同时,调用其 task, 并传入 resolve和reject 方法,当 task 的异步操做执行成功后,就会调用 resolve,也就是执行 Promise .onResolvedCallbacks 数组中的回调,执行失败时同理。
- resolve和reject 方法 接收一个参数value,即异步操做返回的结果,方便传值。
2.Promise.prototype.then链式支持
/**
* onFulfilled成功的回调,onReject失败的回调
* 原型链方法
*/
then(onFulfilled, onRejected) {
let self = this;
// 当调用时没有写函数给它一个默认函数值
onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value;
onRejected = isFunction(onRejected) ? onRejected : value => {
throw value
};
let promise2;
if (self.status == 'resolved') {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
if (self.status == 'rejected') {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
if (self.status == 'pending') {
promise2 = new Promise((resolve, reject) => {
self.onResolvedCallbacks.push(value => {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push(value => {
try {
let x = onRejected(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
return promise2;
}
复制代码
代码思路与要点:
- 调用 then 方法,将成功回调放入 promise.onResolvedCallbacks 数组;失败回调放入 promise.onRejectedCallbacks 数组
- 返回一个 Promise 实例 promise2,方便链式调用
- then方法中的 return promise2 实现了链式调用
- 若是传入的是一个不包含异步操做的函数,resolve就会先于 then 执行,即 promise.onResolvedCallbacks 是一个空数组,为了解决这个问题,在 resolve 函数中添加 setTimeout,将 resolve 中执行回调的逻辑放置到 JS 任务队列末尾;reject函数同理。
3.静态方法Promise.resolve
static resolve(value) {
return new Promise((resolve, reject) => {
if (typeof value !== null && typeof value === 'object' && isFunction(value.then)) {
value.then();
} else {
resolve(value);
}
})
}
复制代码
静态方法Promise.resolve(value)
能够认为是 new Promise()
方法的快捷方式。
好比 Promise.resolve(666);
能够认为是如下代码的语法糖。
new Promise(function(resolve){
resolve(666);
});
复制代码
4.静态方法Promise.reject
static reject(err) {
return new Promise((resolve, reject) => {
reject(err);
})
}
复制代码
Promise.reject(err)
是和 Promise.resolve(value)
相似的静态方法,是 new Promise()
方法的快捷方式。
好比 Promise.reject(new Error("出错了"))
就是下面代码的语法糖形式。
new Promise(function(resolve,reject){
reject(new Error("出错了"));
});
复制代码
4.静态方法Promise.all
/**
* all方法,能够传入多个promise,所有执行完后会将结果以数组的方式返回,若是有一个失败就返回失败
* 静态方法为类本身的方法,不在原型链上
*/
static all(promises) {
return new Promise((resolve, reject) => {
let result = []; // all方法最终返回的结果
let count = 0; // 完成的数量
for (let i = 0; i < promises.length; i++) {
promises[i].then(data => {
result[i] = data;
if (++count == promises.length) {
resolve(result);
}
}, err => {
reject(err);
});
}
});
}
复制代码
Promise.all
接收一个 promise对象的数组做为参数,当这个数组里的全部promise对象所有变为resolve或reject状态的时候,它才会去调用.then
方法。当所有为resolve时返回一个所有的resolve执行结果数组,只要有一个不为resolve状态,直接返回这个状态的执行失败结果。
5.静态方法Promise.race
/**
* race方法,能够传入多个promise,返回的是第一个执行完的resolve的结果,若是有一个失败就返回失败
* 静态方法为类本身的方法,不在原型链上
*/
static race(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(data => {
resolve(data);
},err => {
reject(err);
});
}
});
}
复制代码
Promise.race
和Promise.all
相相似,它一样接收一个数组,race的意思是竞赛,顾名思义只要是竞赛就有惟一的那个第一名,因此它与all最大的不一样是只要该数组中的任意一个 Promise 对象的状态发生变化(不管是 resolve 仍是 reject)该方法都会返回,因此它只输出某一个最早执行的状态结果,而不是像all同样在所有为resolve状态时返回的是一个数组。只需在Promise.all 方法基础上修改一下就可实现race。
源代码 以上是对几个主要方法的介绍,还有些没有介绍彻底,能够参考源代码,源码文件里包含了一个测试文件夹以及es5的版本源码,后续会奉上更为详尽的解释。另外能够经过安装一个插件来对实现的promise进行规范测试。
npm(cnpm) i -g promises-aplus-tests
promises-aplus-tests es6Promise.js
复制代码