ES6 Promise的使用和理解
栏目: JavaScript · 发布时间: 6年前
内容简介:JS语言的执行环境是“单线程”的,即指一次只能完成一件任务;如果有多个任务,那么必须排队,前面一个任务完成,再执行后一个任务,以此类推。这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步
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(null,data=>{ throw new Error('出错') }).then(data=>{ console.log(data) }); 复制代码
这里我们将内容处理后,则将一个普通值传给了下次的then作为公共的过,然后在下一步中没有处理成功,
那么再传入下一个then,最终会作为成功值打印出来。
最后我们看一下对错误的处理,在处理完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+规范
希望这篇文章能帮到你,以上
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 关于golang指针的理解与使用
- 如何理解并正确使用 MySQL 索引
- 使用 Go 语言来理解 Tensorflow
- 正确理解 memcached,才能更好的使用
- java NIO理解分析与基本使用
- Vuex新手的理解与使用 原 荐
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Scalability Rules
Martin L. Abbott、Michael T. Fisher / Addison-Wesley Professional / 2011-5-15 / USD 29.99
"Once again, Abbott and Fisher provide a book that I'll be giving to our engineers. It's an essential read for anyone dealing with scaling an online business." --Chris Lalonde, VP, Technical Operatio......一起来看看 《Scalability Rules》 这本书的介绍吧!