解决异步编程回调地狱问题的解决方案Promise、Generator和async/await已被ES6收录,发布/订阅和deferred对象是社区中的解决方案。本篇文章重在深入理解Promise,手写一个Promise方法。
序号 | 资料名称 | 链接 | 备注 |
---|---|---|---|
1 | Promise介绍 | https://es6.ruanyifeng.com/#docs/promise | |
2 | promise 基础 | https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise | |
3 | promises A+规范 | https://github.com/promises-aplus/promises-spec | |
4 | promises A+规范测试工具用例工具 | https://github.com/promises-aplus/promises-tests |
处理多个异步请求( 回调嵌套/回调地狱/回调金字塔 )
当我们使用第三方库提供的异步方法时,我们希望有以下几点(提高可靠性):
1. 不会太早调用我的回调函数;
2. 不会太迟调用我的回调函数;
3. 会给我的回调提供必要的参数;
4. 在我的回调失败的时候会提醒我。
当我们使用多个第三方库,而其中的库又调用其他的库的异步方法时,可靠性丢失的问题尤为重要。Promise就是解决这个问题的。
一个 Promise 必然处于以下几种状态之一:
待定(pending):初始状态,既没有被兑现,也没有被拒绝。
已兑现(fulfilled):意味着操作成功完成。
已拒绝(rejected):意味着操作失败。
待定状态的 Promise 对象要么会通过一个值被兑现,要么会通过一个原因(错误)被拒绝。当这些情况之一发生时,我们用 promise 的 then 方法 排列起来的相关处理程序就会被调用。如果 promise 在一个相应的处理程序被绑定时就已经被兑现或被拒绝了,那么这个处理程序也同样会被调用,因 此在完成异步操作和绑定处理方法之间不存在竞态条件。
因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回的是 promise,所以它们可以被链式调用。
一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知值的代理。
它让你能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。
这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。
通过Promise,我们重新获得了程序的控制权,而不是通过给第三方库传递回调来转移控制权。
更为详细的内容可查看学习资料链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
1. 无法取消Promise,一旦新建他就会立即执行,无法中途取消;
2. 如果不设置回调函数,Promise内部抛出的错误,不会反映到外部;
3. 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
参看学习资料1链接:https://es6.ruanyifeng.com/#docs/promise
实现一个简单的Promise-polyfill(polyfill用于实现浏览器并不支持原生API的代码)。
promise/A+规范是一个开放的标准,对于开发人员可互操作的JavaScript promise。
promise状态要求:
1. promise对象代表一个异步操作,有三种状态:pengding(进行中)、fulfilled(已成功)和reject(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
then方法要求:
1. 一个promise必须提供一个then方法,用来获取当前异步操作的value或error。
2. 一个promise的then方法接收两个参数:promise.then(onFulfilled, onRejected)。
3. then方法返回的是一个新的promise实例(注意,不是原来那个promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
4. onFulfilled, onRejected有可能返回的还是一个promise对象(即有异步操作),这时 后一个回调函数,就会等待该promise对象的状态发生变化,才会被调用。
promises A+规范:https://github.com/promises-aplus/promises-spec
//(Promise-polyfill)手写符合Promise/A+规范的Promise
//promise.js
//(Promise-polyfill)手写符合Promise/A+规范的Promise
//promise.js
var cache = {}; // 缓存对象
var container = function (flags) {
//flags标记 once;memory;stopOnFalse
// once只执行一次;
// memory调用startup之后再调用startup会立即执行(也就是不在执行上边已经执行过的了);
// stopOnFalse遇到暂停属性的值为false时,暂停执行(也就是设置了stopOnFalse的值为false,后边的回调就不执行了)
flags = typeof flags == "string" ? (cache[flags] || createFlags(flags)) : extend({}, flags);
var carryOut, len, i, memory, stackLen, stackPoint;
//carryOut记录回调是否调用过;
//len是stack的长度;
//stackLen记录最后一次执行add方法前的stack的长度;
//stackPoint是当flags.memory存在时,记录下stackLen的值;
var stack = []; //回调列表
var fire = function (data) {
memory = flags.memory && data; //判断flags中是否有memory这个属性(也就是外部使用container方法时,有没有传这个属性),有的话将data(fire方法需要的参数)赋值给memory这个变量;
carryOut = true; //设为true,阻止下次调用
len = stack.length;
i = stackPoint || 0;
for (; i < len; i++) {
if (stack[i].apply(data[0], data[1]) === false && flags.stopOnFalse) { //如果其中一个回调函数的返回值是false,同时设置了stopOnFalse标记
break;
}
}
};
var self = {
add: function () {
(function add(args) {
stackLen = stack.length;
Array.from(args).forEach(function (arg) {
if (toString.call(arg) === "[object Function]") {
if (!self.has(arg)) { // 检测是否有重复的回调
stack.push(arg);
} else if (arg && arg.length && typeof arg == "string") {
add(arg);
}
}
})
})(arguments);
if (memory) {
stackPoint = stackLen; //当flags.memory存在时,记录下stackLen的值
fire(memory);
}
},
startupWith: function (context, args) { //context上下文对象,args参数
args = args || [];
args = [context, args];
if (!flags.once || !carryOut) {
fire(args); //依次调用回调列表中的回调
}
},
startup: function () {
self.startupWith(this, arguments); //这里的this指向self
},
has: function (fn) {
return stack.indexOf(fn) > -1;
}
}
return self;
}
function createFlags(flags) {
var res = {};
//在空白处拆分
(flags.match(/\S+/g) || []).forEach(function (flag) {
res[flag] = true;
});
return res;
}
function extend(to, from) { //扩展一份flags对象
for (let key in from) {
to[key] = from[key];
}
return to;
}
function Promise(func) {
var tuples = [
// resolve状态
// success,fail添加回调语法糖
// container创建容器->回调列表
// resolved,rejected最终状态
["resolve", "success", container("once memory"), "resolved"],
["reject", "fail", container("once memory"), "rejected"]
];
var state = "penging";
var chain = {
then: function ( /* fnSuccess, fnFail */ ) {
var args = arguments;
return Promise(function (resolve, reject) {
tuples.forEach(function (tuple, i) {
var fn = toString.call(args[i]) === "[object Function]" && args[i];
chain[tuple[1]](function () {
var returned = fn && fn.apply(this, arguments);
if (returned && toString.call(returned.then) === "[object Function]") {
returned.success(resolve);
returned.fail(reject)
}
})
})
})
}
};
var protect = {}; //保证私密性
tuples.forEach(function (tuple, i) {
var list = tuple[2]; //创建容器,创建两次,一次是成功,一次是失败
var stateString = tuple[3];
if (stateString) {
list.add(function () {
state = stateString;
})
}
chain[tuple[1]] = list.add; //分别绑定成功的add方法和失败的add方法
// 状态绑定(Promise A+规范中规定:chain的状态外部是无法改变的)
protect[tuple[0]] = function () {
protect[tuple[0] + "Witn"](this, arguments);
}
protect[tuple[0] + "Witn"] = list.startupWith;
})
if (func) {
func.call(chain, protect["resolve"], protect["reject"]);
}
return chain;
}
1. promise表示一个异步操作的最终结果;
2. promise并不是通过移除回调来解决“回调地狱”的问题;
3. promise所做的只是改变了你传递回调的地方。提供中立promise机制,你就能重新获得程序的控制权。
革命尚未成功,同志仍需努力。