老生常谈:Promise 用法与源码分析
栏目: JavaScript · 发布时间: 6年前
内容简介:Promise本身是一个异步编程的方案,让处理过程变得更简单。es6引入promise特性来处理JavaScript中的异步场景。以前,处理异步最常用的方法就是回调函数,但是当过程稍微复杂一点,多个异步操作集中在一起的时候,就容易出现一个Promise可以避免这种情况发生,将Promise有4种状态:
Promise本身是一个异步编程的方案,让处理过程变得更简单。es6引入promise特性来处理JavaScript中的异步场景。以前,处理异步最常用的方法就是回调函数,但是当过程稍微复杂一点,多个异步操作集中在一起的时候,就容易出现一个 回调金字塔 的情况,可读性和可维护性都非常差,比如:
setTimeout(function () { console.log('ping'); setTimeout(function () { console.log('pong'); setTimeout(function () { console.log('end!'); }, 1000); }, 1000); }, 1000); 复制代码
Promise可以避免这种情况发生,将 回调嵌套 转变为 链式调用 ,避免回调金字塔的出现。
Promise基本用法
Promise有4种状态:
- fulfilled ——成功状态
- rejected ——失败状态
- pending ——执行状态(未成功也未失败)
- settled ——完成状态
let promise = new Promise((resolve, reject) => { // when success, resolve let value = 'success'; resolve(value); // when an error occurred, reject reject(new Error('Something happened!')); }); 复制代码
可以通过then方法来处理返回的结果
// promise.then(onResolve, onReject) promise.then(response => { console.log(response); }, error => { console.log(error); }); 复制代码
then方法不仅仅是处理结果,而且还可以继续返回promise对象
promise.then(response => { console.log(response); // success return 'another success'; }).then(response => { console.log(response); // another success }); 复制代码
对reject状态返回的结果的处理,可以通过then的第二个参数,也可以通过catch方法
promise.then( null, error => { console.log(error); // failure } ); // 或 promise.catch(err => { console.log(err); // failure }); 复制代码
同时处理多个promise,不关注执行顺序可以用all方法
let doSmth = new Promise(resolve => { resolve('doSmth'); }), doSmthElse = new Promise(resolve => { resolve('doSmthElse'); }), oneMore = new Promise(resolve => { resolve('oneMore'); }); Promise.all([ doSmth, doSmthElse, oneMore ]) .then(response => { let [one, two, three] = response; console.log(one, two, three); // doSmth doSmthElse oneMore }); 复制代码
Promise.all()接收一个promises数组,当全部fulfiled时,返回一个按顺序的数组 当其中一个reject时,返回第一个rejected的值 或者race方法,接收多个promise实例,组成一个新的promise,有一个变化的时候,外层promise跟着变化。 快捷方法:
- Promise.resolve(value) 返回一个resolve(value)的promise 或直接返回这个value如果value本身时promise对象的话。
- Promise.reject(value) 返回一个rejected状态的promise,并且reject(value)
——参考ES6 Promise
原理
为了学习promise内部原理,最好是看其实现源码, then/promise 是github上一个遵循promise A+规范的库,其核心代码在core文件中。那么就从这个库来学习。
function noop() {} // 定义一个空函数用于对比和实例化空promise,后面会用到 // States: // 库定义的4种状态 // 0 - pending // 1 - fulfilled with _value // 2 - rejected with _value // 3 - adopted the state of another promise, _value var LAST_ERROR = null; var IS_ERROR = {}; // 这两个用来捕获错误 // 获取obj中的then方法 function getThen(obj) { try { return obj.then; } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } // 当then中只传进了一个回调函数时调用此方法 function tryCallOne(fn, a) { try { return fn(a); } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } // 当then中传入了两个回调函数时调用此方法 function tryCallTwo(fn, a, b) { try { fn(a, b); } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } 复制代码
// Promise构造函数 function Promise(fn) { // 检验是否实例化了promise对象,不能直接使用promise构造函数来封装自己的代码 if (typeof this !== 'object') { throw new TypeError('Promises must be constructed via new'); } // 检验传进来的是否为函数,promise必须接受一个函数来进行实例化 if (typeof fn !== 'function') { throw new TypeError('Promise constructor\'s argument is not a function'); } this._deferredState = 0; // 与后面的this._deferreds关系密切,当resolve方法接收的是一个promise时,回用到他们 this._state = 0; // 对应上方4种状态 this._value = null; // 存放最终结果 this._deferreds = null; // 存放then中接收的处理函数 if (fn === noop) return; // 如果promise接收的是空函数,直接返回,结束。 doResolve(fn, this); } 复制代码
可以看到,我通过Promise构造函数实例化一个promise对象,在对参数进行检查后,我们会执行doResolve(fn, this)方法,顺藤摸瓜看看doResolve函数做了什么
function doResolve(fn, promise) { var done = false; // 确保onFulfilled 和 onRejected只被调用一次 var res = tryCallTwo(fn, function (value) { if (done) return; done = true; resolve(promise, value); }, function (reason) { if (done) return; done = true; reject(promise, reason); }); if (!done && res === IS_ERROR) { done = true; reject(promise, LAST_ERROR); } } 复制代码
这里就是将两个回调函数分别传给 fn 的 两个参数,并确保他们只执行一次。 接下来就要看它的resolve方法。
function resolve(self, newValue) { if (newValue === self) { return reject( self, new TypeError('A promise cannot be resolved with itself.') ); } if ( newValue && (typeof newValue === 'object' || typeof newValue === 'function') ) { var then = getThen(newValue); if (then === IS_ERROR) { return reject(self, LAST_ERROR); } if ( then === self.then && newValue instanceof Promise ) { // 当接收的参数为promise,或thenable对象时。 self._state = 3; self._value = newValue; finale(self); // 执行_deferreds 中的方法,如果有的话。 return; } else if (typeof then === 'function') { doResolve(then.bind(newValue), self); return; } } self._state = 1; self._value = newValue; finale(self); } 复制代码
resolve除了一些判断外,就是根据接收到的参数的类型来修改state的值。如果接收到promise对象或thenable对象,state转为3,并使用它的结果,如果时其他如字符串类型等,state转为1,直接使用该值。 有了结果之后,就要看一下then方法了。
Promise.prototype.then = function(onFulfilled, onRejected) { if (this.constructor !== Promise) { return safeThen(this, onFulfilled, onRejected); } var res = new Promise(noop); handle(this, new Handler(onFulfilled, onRejected, res)); return res; }; function safeThen(self, onFulfilled, onRejected) { return new self.constructor(function (resolve, reject) { var res = new Promise(noop); res.then(resolve, reject); handle(self, new Handler(onFulfilled, onRejected, res)); }); } 复制代码
then方法也很简单,就是用Handler包装一个对象
function Handler(onFulfilled, onRejected, promise){ this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; this.onRejected = typeof onRejected === 'function' ? onRejected : null; this.promise = promise; } 复制代码
然后调用handle方法。整个过程就是创建一个新的promise,调用handle方法,将新的promise返回,以便实现链式调用。 下面看一下handle方法。
function handle(self, deferred) { // self 移动指向最新的promise while (self._state === 3) { self = self._value; } if (Promise._onHandle) { Promise._onHandle(self); } if (self._state === 0) { // 向_deferredState中添加handler处理过得对象,也就是{onFulfilled,onRejected,promise} if (self._deferredState === 0) { self._deferredState = 1; self._deferreds = deferred; return; } if (self._deferredState === 1) { self._deferredState = 2; self._deferreds = [self._deferreds, deferred]; return; } self._deferreds.push(deferred); return; } handleResolved(self, deferred); } 复制代码
handle方法就是根据state的值和_deferredState ,来决定要做的事情,我们来捋一捋,当我们的resolve方执行,state转为1时,我们会进入then方法,然后进入handle方法,因为state为1,可以看到我们会直接进入handleResolved方法。
resolve -> then -> handle -> handleResolved 复制代码
看看handleResolved函数是做什么的
function handleResolved(self, deferred) { asap(function() { var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; if (cb === null) { if (self._state === 1) { resolve(deferred.promise, self._value); } else { reject(deferred.promise, self._value); } return; } var ret = tryCallOne(cb, self._value); if (ret === IS_ERROR) { reject(deferred.promise, LAST_ERROR); } else { // 此处主要服务于promise的链式调用,因为promise通过返回一个新的promise来实现链式调用。 // 新的promise保存在deferred.promise中 resolve(deferred.promise, ret); } }); } 复制代码
过滤掉添加判断,handleResolved就是使用结果值self._value调用then中的相应回调(成功或失败)。 那当resolve接收的是普通值得时候整个运行过程就知道了。
resolve -> then -> handle -> handleResolved -> 执行onFulfilled或onRejected 复制代码
当我们resolve接收到得是一个promise或thenable对象时,我们进入到handle后,会进入while循环,直到self指向接收到的promise,以接收到的promise的结果为标准,在接收到的promise的 state===0 阶段我们会将原始promise中拿到得onFulfilled以及onRejected回调方法(包含在deferred对象中),添加到接收到的promise的 _deferreds 中,然后return。 存在 _deferreds 中的回调在什么时候执行呢? 我们可以看到无论时resolve还是reject,只要状态改变都会执行 finale 方法,我们看一下 finale
function finale(self) { if (self._deferredState === 1) { handle(self, self._deferreds); self._deferreds = null; } if (self._deferredState === 2) { for (var i = 0; i < self._deferreds.length; i++) { handle(self, self._deferreds[i]); } self._deferreds = null; } } 复制代码
因为每次执行此方法都是在state状态改变的时候,所以进入handle函数后会直接进入handleResolved方法,然后使用self._value的结果值执行对应的回调函数(onFulfilled 或 onRejected)。 最后看看reject
function reject(self, newValue) { self._state = 2; self._value = newValue; if (Promise._onReject) { Promise._onReject(self, newValue); } finale(self); } 复制代码
这下清晰多了,再来捋一捋,
总结
- Promise本身是一个异步编程的方案,让处理过程变得更简单。es6引入promise特性来处理JavaScript中的异步场景,代替了传统基于回调的方案,防止了如回调金字塔等现象的发生。
- promise内部运行机制:使用promise封装异步函数,通过resolve和reject方法来处理结果,
- 当发生错误时,reject会将state转为状态2(rejected)并调用对应的onRejected回调函数,
- 当成功时,resolve接收对应的结果,当结果时普通值(比如string类型)他会将state转为状态1,直接使用该值调用对应的onFulfilled回调函数,
- 当接收到的是一个promise对象或者thenable对象时,会将thenable对象转为promise对象,并将当前state转为3,将我们的onFulfilled和onRejected回掉函数保存到接收到的promise中,并采用接收到的promise的结果为最终标准,当它的state发生变化时,执行相应的回调函数。
- 其链式调用时通过返回一个新的promise空对象来实现的,在当前的onFulfilled或onRejected回调执行后,会将执行结果以及新的promise作为参数去调用onFulfilled或onRejected方法,实现值在链式中的传递。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 老生常谈分布式锁
- 老生常谈,HashMap的死循环
- 老生常谈——Android冷启动优化
- 《iOS面试题 - 老生常谈》之提示答案
- 老生常谈,TCP 为啥要三次握手?
- 老生常谈,TCP 为啥要三次握手?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入浅出 MFC 第二版
侯俊杰 / 松岗 / 1997.05
深入浅出MFC是一本介绍 MFC(Microsoft Foundation Classes)程式设计技术的书籍。对於 Windows 应用软体的开发感到兴趣,并欲使用 Visual C++ 整合环境的视觉开发工具,以 MFC 为程式基础的人,都可以从此书获得最根本最重要的知识与实例。 如果你是一位对 Application Framework 和物件导向(Object Orien......一起来看看 《深入浅出 MFC 第二版》 这本书的介绍吧!