一个 Promise 的运用:
var firstPromise = new Promise(function(resolve,reject){ setTimeout(function(){ var result = Math.random() <= 0.5 ? 1:0; if(result){ resolve('resolved'); }else{ reject('rejected') } },1000) }) var secondPromise = new Promise(function(resolve,reject){ setTimeout(function(){ var result = Math.random() <= 0.5 ? 1:0; if(result){ resolve('resolved'); }else{ reject('rejected') } },2000) }) firstPromise.then(function(value){ console.log(value); return secondPromise; },function(reason){ console.log(reason); return secondPromise; }).then(function(value){ console.log(value); },function(reason){ console.log(reason); }) // 1s后随机输出结果 resolved 或者 rejected // 再1s后随机输出结果 resolved 或者 rejected
效果如上,在一个 promise 被完成/被拒绝时执行对应的回调取到异步结果。
同时,以上代码使用 promise 避免了回调地狱,规范了回调操作。
接下来,把 promise 拆成几块,学习一下怎么样的实现过程。
步骤一、Promise 构造函数
创建 promise 对象的构造函数,是创造 promise 的工厂。
基础要求:Promise 函数仅产生一个对象,避免大量变量的污染,将该藏好的对象/值藏好,该暴露的暴露;Promise 接收一个函数作为参数,且该函数在构造 promise 对象时被执行;Promise 必须有个 .then 方法(后续方法可自行扩展)。
function Promise(fn){ this.then = function(){ }; }
步骤二、初始化过程,处理参数fn
Promise 构造函数参数 fn 中传入 resolve/reject;Promise 初始化的时候执行 fn 并在 promise 得到最终结果后执行传入的 resolve/reject ;resolve/reject 函数中执行 promise 中指定的完成/拒绝时回调函数,并以最终结果作为参数。
function Promise(fn){ // 完成时 function resolve(value) { console.log('value ',value); } // 拒绝时 function reject(reason) { console.log('reason ',reason); } // 执行传入的fn function init(fn, onResolved, onRejected) { try { fn(function (value) { onResolved(value); }, function (reason) { onRejected(reason); }) } catch (err) { onRejected(err); } } init(fn, resolve, reject); this.then = function(){ }; } var promise = new Promise(function(resolve,reject){ setTimeout(function(){ var result = Math.random() <= 0.5 ? 1:0; if(result){ resolve('resolved') }else{ reject('rejected') } },1000) }) // 1s后随机输出 value resolved 或者 reason rejected
步骤三、.then 里的处理流程
在promise中, .then 将传入的 resolvedHandle 和 rejectedHandle 函数存入 promise 的 handlers 中作为回调列表中的一项,在需要的时候(Promise被完成的时候)携带最终结果执行。
首先,假设有个异步操作,而且已经知道回调函数是什么,代码如下:
var resolvedHandle = function(res){ console.log(res) }; var rejectedHandle = function(err){ console.log(err) }; setTimeout(function(){ var result = Math.random() <= 0.5 ? 1:0; if(result){ resolvedHandle('resolved'); }else{ rejectedHandle('rejected'); } },1000) // 1s后输出 resolved 或者 rejected
而对于 promise 而言,回调函数是在 .then 中传入并且在 promise 中给定义的,并且为了实现链式的操作, .then 中必须有返回一个对象,且对象须是一个携带 .then 方法的对象或函数或为一个 promise ,才足以继续执行.then。
// fn 作为初始化Promise时传入的函数,应该被立即执行并取出其中的调用 function Promise(fn) { var $resolveHandle = function (res) { }; var $rejectHandle = function (err) { }; // 执行Promise被完成时函数 function resolve(value) { try { var then = getThen(value); if (then) { init(then.bind(value), resolve, reject); return; }; fulfill(value); } catch (err) { reject(err); } } // 完成时 function fulfill(value) { $resolveHandle(value); $resolveHandle = null; } // 拒绝时 function reject(reason) { $rejectHandle(reason); $rejectHandle = null; } // 执行传入的fn并执行回调 function init(fn, onResolved, onRejected) { try { fn(function (value) { onResolved(value); }, function (reason) { onRejected(reason); }) } catch (err) { onRejected(err); } } init(fn, resolve, reject); function getThen(value) { var t = typeof value; if (value && (t === 'object' || t === 'function')) { var then = value.then; if (typeof then === 'function') { return then; } } return null; }; this.then = function (resolveHandle, rejectHandle) { return new Promise(function (resolve, reject) { $resolveHandle = function (result) { resolve(resolveHandle(result)); } $rejectHandle = function (reason) { if(rejectHandle){ resolve(rejectHandle(reason)); }else{ reject(reason) } } }) } } var firstPromise = new Promise(function (resolve, reject) { setTimeout(function () { var result = Math.random() <= 0.5 ? 1 : 0; if (result) { resolve('resolved'); } else { reject('rejected'); } }, 1000); }) var secondPromise = new Promise(function (resolve, reject) { setTimeout(function () { var result = Math.random() <= 0.5 ? 1 : 0; if (result) { resolve('resolved 2'); } else { reject('rejected 2'); } }, 2000); }) firstPromise.then(function (res) { console.log('res ', res); return secondPromise; }).then(function (res) { console.log('res 2 ', res); }, function (err) { console.log('rej 2 ', err); }) // 1s后随机输出 res resolved 或者 rej rejected // 又1s后输出 res 2 resolved 2 或者 rej 2 rejected 2 或者 rej 2 rejected
至此,上面的代码基本算是满足了一个 promise 的实现思路,但离正规军 promise 实现还存在一段距离
o(╥﹏╥)o...接下去学习吧。
步骤四、Promise/A+规范
由于 Promise/A+规范较长,就不放到文章里了,给链接吧(中午版是自己翻译的,有出入的地方还请以英文原版为准)
对照promise/A+规范,以上的Promise代码还存在问题:
1.promise还需要存储promise状态和最终结果,以便后续被多次使用;
2.同一个promise的.then方法中注册的回调函数可被多次执行,且回调函数可以是个列表;
3.事件调度,回调函数应该在本轮.then方法所在事件队列结束后被调用;
4.捕捉错误并做拒绝处理;
更多细节...
继续改进,最后整改后的代码大致是这样的:
function Promise(fn) { /* state * 0 : pending * 1 : resloved * 2 : rejected */ var state = 0; var value = null; var handlers = []; function fulfill(result) { state = 1; value = result; handlers.forEach(handle); handlers = []; }; function reject(error) { state = 2; value = error; handlers.forEach(handle); handlers = []; }; function resolve(result) { try { var then = getThen(result); if (then) { init(then.bind(result), resolve, reject); return; } fulfill(result); } catch (err) { reject(err); } }; function getThen(value) { var type = typeof value; if (value && (type === 'object' || type === 'function')) { var then = value.then; if (typeof then === 'function') { return then; } } return null; }; function handle(handler) { if (state === 0) { handlers.push(handler); } else { if (typeof handler.onResolved === 'function') { if (state === 1) { handler.onResolved(value); }; if (state === 2) { handler.onRejected(value); }; } } }; // 放到事件队列最后,在本轮事件执行完后执行 function timeHandle(callback, newValue) { setTimeout(function () { callback(newValue); }, 0) } function init(fn, onResolved, onRejected) { try { fn(function (value) { timeHandle(onResolved, value); }, function (reason) { timeHandle(onRejected, reason); }); } catch (err) { timeHandle(onRejected, err); } }; init(fn, resolve, reject); this.then = function (onResolved, onRejected) { if (!onResolved && !onRejected) { throw new TypeError('One of onResolved or onRejected must be a function.') }; return new Promise(function (resolve, reject) { handle({ onResolved: function (result) { if (typeof onResolved === 'function') { try { resolve(onResolved(result)); } catch (err) { reject(err); } } else { resolve(result); } }, onRejected: function (error) { if (typeof onRejected === 'function') { try { resolve(onRejected(error)); } catch (err) { reject(err); } } else { reject(error); } } }) }) }; } var firstPromise = new Promise(function (resolve, reject) { setTimeout(function () { var result = Math.random() <= 0.5 ? 1 : 0; if (result) { resolve('resolved 1'); } else { reject('rejected 1'); } }, 1000); }) var secondPromise = new Promise(function (resolve, reject) { setTimeout(function () { var result = Math.random() <= 0.5 ? 1 : 0; if (result) { resolve('resolved 2'); } else { reject('rejected 2'); } }, 3000); }) firstPromise.then(function (res) { console.log('res 1 ', res); return secondPromise; }, function (err) { console.log('rej 1 ', err); return secondPromise; }).then(function (res) { console.log('res 2 ', res); }, function (err) { console.log('rej 2 ', err); }) /* * * 1s后输出 res 1 resolved 1 或者 rej 1 rejected 1 * 2s后输出 res 2 resolved 2 或者 rej 2 rejected 2 * */
通过板块一、二、三的知识点,即可大致摸清promise的实现;板块四加上一些补充和限制,遵循一些规范,提高promise功能的可扩展性。
学会了怎么理解promise,更重要的是学会正确的使用它。
正确使用 Promise
promise 在业务开发中多用来处理异步或者多层回调的情况。
基础使用 Promise MDN 及相关介绍文档中的案例为准,这里不一一赘述了... 这里简单的列出两个在使用 promise 过程中比较需要注意的点:
1. 不同平台环境下 Promise 的方法和遵循规则略微有些出入,详情以各平台环境下的 Promise 对象为基准。
如 es6 Promise 存在Promise.race,Promise.all等方法,node中则没有这些方法。
如 浏览器 Promise 事件调度走的是 setTimeout,node 走的是 process.nextTick 。(参考 [asap] )
2. Promise 虽可解决回调操作的规范问题(回调地狱),但也不能滥用 Promise (可能会占用过多内存)。
promise 解决后的结果会被存于内存中,被对应 promise 引用着,将上面的最终代码中测试的两个 promise 改成如下:
var firstPromise = new Promise(function (resolve, reject) { setTimeout(function () { var result = Math.random() <= 0.5 ? 1 : 0; var str = ''; var i = 0, num = 500000; for (; i < num; i++) { str += 'promise ' } if (result) { resolve('resolved 1 : ' + str); } else { reject('rejected 1 : ' + str); } }, 1000); })
则内存占用情况如下:
这些是一些平台差异或业务需求方面的不同点,对 Promise 核心实现并影响甚微,对 Promise 扩展方法有影响,对业务中 Promise 的使用有影响。
参考