ES6 Promise的使用和理解
JS的异步
JS语言的执行环境是“单线程”的,即指一次只能完成一件任务;如果有多个任务,那么必须排队,前面一个任务完成,再执行后一个任务,以此类推。这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。
为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。
"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
"异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。
常用的异步编程模式
- 回调函数 即f1,f2两个函数,f2要等待f1执行结果后执行,即 f1(f2)
- 事件驱动的方式 f1.on('done', f2); (JQ写法,f1完成时,trigger("done")则执行f2)
- 发布-订阅 模式
- Promise对象实现
Promise对象
本文着重讲ES6的Promise对象的定义和用法 阮一峰老师的ES6详解 - Promise对象 相信大家在学习ES6的过程中都或多或少的学习过阮老师的ES6教程,那么这里简单举一些例子讲述Promise对象的特点和使用方法
基础使用方法
ES6提供Promise构造函数,我们创造一个Promise实例,Promise构造函数接收一个函数作为参数,这个传入的函数有两个参数,分别是两个函数 resolve
和reject
作用是,resolve
将Promise的状态由未成功变为成功,将异步操作的结果作为参数传递过去;相似的是reject
则将状态由未失败转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。
实例创建完成后,可以使用then
方法分别指定成功或失败的回调函数,比起f1(f2(f3))的层层嵌套的回调函数写法,链式调用的写法更为美观易读
let promise = new Promise((resolve, reject)=>{
reject("拒绝了");
});
promise.then((data)=>{
console.log('success' + data);
}, (error)=>{
console.log(error)
});
执行结果:"拒绝了"
Promise的特点
- 对象不受外界影响,初始状态为pending(等待中),结果的状态为resolve和reject,只有异步操作的结果决定这一状态
- 状态只能由pending变为另外两种的其中一种,且改变后不可逆也不可再度修改, 即pending -> resolved 或 pending -> reject
let promise = new Promise((resolve, reject)=>{
reject("拒绝了");
resolve("又通过了");
});
promise.then((data)=>{
console.log('success' + data);
}, (error)=>{
console.log(error)
});
执行结果: "拒绝了"
上述代码不会再执行resolve的方法
then方法的规则
then
方法下一次的输入需要上一次的输出- 如果一个promise执行完后 返回的还是一个promise,会把这个promise 的执行结果,传递给下一次
then
中 - 如果
then
中返回的不是Promise对象而是一个普通值,则会将这个结果作为下次then的成功的结果 - 如果当前
then
中失败了 会走下一个then
的失败 - 如果返回的是undefined 不管当前是成功还是失败 都会走下一次的成功
- catch是错误没有处理的情况下才会走
then
中不写方法则值会穿透,传入下一个then
中
用node fs模块读取文件的流程来测试 我们创建一个读取文件的方法,在Promise中定义如果读取成功则展示文件的内容,否则报出错误
let fs = require('fs');
function read(file, encoding) {
return new Promise((resolve, reject)=>{
fs.readFile(filePath, encodeing, (err, data)=> {
if (err) reject(err);
resolve(data);
});
})
}
由于想看到多次连贯回调,我们专门设置3个txt文件,其中1号文件的内容为2号文件的文件名,2号文件的内容为3号文件的文件名,3号中展示最终内容

执行代码如下:
read('1.promise/readme.txt', 'utf8').then((data)=>{
console.log(data)
});
读取一个文件的打印结果为,readme2.txt 我们改造这个代码,添加多个回调,在最后一个之前的所有then中都return出当前返回的promise对象
read('readme.txt', 'utf8').then((data)=>{
return read(data, 'utf8');
}).then((data)=>{
return read(data, 'utf8')
}).then((data)=>{
console.log(data);
});
最终输出 readme3.txt的内容


再对下一步then进行新的处理,我们对readme3.txt的内容进行加工并返回
read('readme.txt', 'utf8').then((data)=>{
return read(data, 'utf8');
}).then((data)=>{
return read(data, 'utf8')
}).then(data=>{
return data.split('').reverse().join(); // 这一步返回的是一个普通值,普通值在下一个then会作为resolve处理
}).then(null,data=>{ // 特意不对成功做处理,放过这一个值,进入下一步
throw new Error('出错') // 由于上一步返回的是普通值,走成功回调,不会走到这里
}).then(data=>{
console.log(data) // 最终会打印出来上上步的普通值
});
这里我们将内容处理后,则将一个普通值传给了下次的then,但是由于下一个then没有处理成功方法(null)

最后我们看一下对错误的处理,在处理完readme3.txt的结果后,我们将这个值传入下一个then中,令其作为一个文件名打开,然而此时已经找不到这个不存在的文件了,那么在最后一步就会打印出报错的结果
read('readme.txt', 'utf8').then((data)=>{
return read(data, 'utf8');
}).then((data)=>{
return read(data, 'utf8')
}).then(data=>{
return data.split('').reverse().join();
}).then(null,data=>{
throw new Error('出错')
}).then(data=>{
return read(data, 'utf8')
}).then(null,(err)=>{
console.log(err)
});
结果:

Promises A+ (Promises Aplus)
Promises Aplus规范即规定了Promise的原理,源代码规范等,通过这个规范,我们可以自己实现一个基于PromiseA+规范的Promise类库,下面我们展示一下源码的实现
/**
* Promise 实现 遵循promise/A+规范
* 官方站: https://promisesaplus.com/
* Promise/A+规范译文:
* https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#note-4
*/
// promise 三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function Promise(excutor) {
let self = this; // 缓存当前promise实例对象
self.status = PENDING; // 初始状态
self.value = undefined; // fulfilled状态时 返回的信息
self.reason = undefined; // rejected状态时 拒绝的原因
self.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数
self.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数
function resolve(value) { // value成功态时接收的终值
if(value instanceof Promise) {
return value.then(resolve, reject);
}
// 为什么resolve 加setTimeout?
// 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行.
// 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
setTimeout(() => {
// 调用resolve 回调对应onFulfilled函数
if (self.status === PENDING) {
// 只能由pedning状态 => fulfilled状态 (避免调用多次resolve reject)
self.status = FULFILLED;
self.value = value;
self.onFulfilledCallbacks.forEach(cb => cb(self.value));
}
});
}
function reject(reason) { // reason为失败态时接收的原因
setTimeout(() => {
// 调用reject 回调对应onRejected函数
if (self.status === PENDING) {
// 只能由pedning状态 => rejected状态 (避免调用多次resolve reject)
self.status = REJECTED;
self.reason = reason;
self.onRejectedCallbacks.forEach(cb => cb(self.reason));
}
});
}
// 捕获在excutor执行器中抛出的异常
// new Promise((resolve, reject) => {
// throw new Error('error in excutor')
// })
try {
excutor(resolve, reject);
} catch (e) {
reject(e);
}
}
这一部分代码我们对resolve和reject进行了判断处理,接着我们构造then
方法
/**
* [注册fulfilled状态/rejected状态对应的回调函数]
* @param {function} onFulfilled fulfilled状态时 执行的函数
* @param {function} onRejected rejected状态时 执行的函数
* @return {function} promise2 返回一个新的promise对象
*/
Promise.prototype.then = function (onFulfilled, onRejected) {
// 成功和失败的回调 是可选参数
// onFulfilled成功的回调 onRejected失败的回调
let self = this;
let promise2;
// 需要每次调用then时都返回一个新的promise
promise2 = new Promise((resolve, reject) => {
// 成功态
if (self.status === 'resolved') {
setTimeout(()=>{
try {
// 当执行成功回调的时候 可能会出现异常,那就用这个异常作为promise2的错误的结果
let x = onFulfilled(self.value);
//执行完当前成功回调后返回结果可能是promise
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
}
// 失败态
if (self.status === 'rejected') {
setTimeout(()=>{
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
}
if (self.status === 'pending') {
// 等待态时,当一部调用resolve/reject时,将onFullfilled/onReject收集暂存到集合中
self.onResolvedCallbacks.push(() => {
setTimeout(()=>{
try {
let x = onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
});
self.onRejectedCallbacks.push(() => {
setTimeout(()=>{
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
});
}
});
return promise2
}
// 其中规范要求对回调中增加setTimeout处理
可以看到resolve和reject都有一个处理新promise的方法resolvePromise,对其进行封装,达到处理不同情况的目的
/**
* 对resolve 进行改造增强 针对resolve中不同值情况 进行处理
* @param {promise} promise2 promise1.then方法返回的新的promise对象
* @param {[type]} x promise1中onFulfilled的返回值
* @param {[type]} resolve promise2的resolve方法
* @param {[type]} reject promise2的reject方法
*/
function resolvePromise(promise2,x,resolve,reject){
if(promise2 === x){ // 如果从onFullfilled中返回的x就是promise2,就会导致循环引用报错
return reject(new TypeError('Chaining cycle'));
}
let called; // 声明避免多次使用
// x类型判断 如果是对象或者函数
if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
// 判断是否是thenable对象
try{
let then = x.then;
if(typeof then === 'function'){
then.call(x,y=>{
if(called) return;
called = true;
resolvePromise(promise2,y,resolve,reject);
},err=>{
if(called) return;
called = true;
reject(err);
});
}else{
// 说明是普通对象/函数
resolve(x);
}
}catch(e){
if(called) return;
called = true;
reject(e);
}
}else{
resolve(x);
}
}
以上基本实现了Promise的基本方法,根据Promise的用法,补充一些类上的方法
// 用于promise方法链时 捕获前面onFulfilled/onRejected抛出的异常
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
resolve(value);
})
}
Promise.prototype.catch = function(onRejected){
// 默认不写成功
return this.then(null,onRejected);
};
/**
* Promise.all Promise进行并行处理
* 参数: promise对象组成的数组作为参数
* 返回值: 返回一个Promise实例
* 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。
*/
Promise.all = function(promises){
return new Promise((resolve,reject)=>{
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
if(++i == promises.length){
resolve(arr);
}
}
for(let i = 0;i<promises.length;i++){
promises[i].then(data=>{ // data是成功的结果
processData(i,data);
},reject);
}
})
}
/**
* Promise.race
* 参数: 接收 promise对象组成的数组作为参数
* 返回值: 返回一个Promise实例
* 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快)
*/
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i = 0;i<promises.length;i++){
promises[i].then(resolve,reject);
}
})
}
最后我们导出方法
module.exports = Promise;
至此一个符合PromiseA+规范的自己写的源码库完成了,可以测试使用这个库替代Promise,以测试是否有逻辑错误等,或者可以使用
npm install promises-aplus-tests -g
promises-aplus-test 文件名
插件来测试该源码是否符合PromiseA+规范
希望这篇文章能帮到你,以上
转载自:https://juejin.cn/post/6844903649454325773