他们都解决了原来代码的多层嵌套或者是不断的callback,使代码看起来更优雅也便于维护。
下面是两个示例:
Promise示例
let promise = new Promise(resolve => {
setTimeout(() => {
resolve('---promise timeout---');
}, 2000);
});
promise.then(value => console.log(value));
RxJS
let stream1$ = new Observable(observer => {
let timeout = setTimeout(() => {
observer.next('observable timeout');
}, 2000);
return () => {
clearTimeout(timeout);
}
});
let disposable = stream1$.subscribe(value => console.log(value));
Promise和Rx这两个模式的思想差别很清晰,一个是流程式,一个是数据响应式。 Promise可以用来贯串一连串单一的流程,而且这个流程是可以无限的,而Rx是用一个数据流来贯串所有操作符,它有一个真正意义上的数据消费者。
我们在哪些场景下用Rx比较方便?
首先是需要源源不断的流出数据的场景,因为Promise是一次性的,不适合做这类工作。 比如说把事件/定时器抽象成Rx的Observable更合适,事件可以响应很多次,定时器也可以响应很多次,我们还可以利用Rx的debounce运算符来进行节流,在频繁触发事件的时候过滤那些重复的。
例如:验证码倒计时用Rx较合适,倒计时过程中要一直更新时间,结束后要重新改变按钮的文字及状态。
其次是可能需要重试的场景,由于Rx有retry或者repeat这种从源头开始的运算符,我们可以用它来执行比如“出错后重试三次”之类动作,而Promise就需要你递归处理了,破坏了then的链式。
例如:请求接口的重试或者是按钮点击多次只生效一次(避免按钮重复点击)
而Promise也有一些优于Rx的场景
例如:提交前要先上传图片或者是一个接口的参数取决于另两个接口的返回。
结论
这两种模式都有自己的想法,所以在使用Rx的时候,不要把它当成Promise来用,记住它的本质是数据响应。 Promise能做的Rx都能做,但是只要能用Promise的就不要用Rx。
new Promise(function (resolve, reject) {
var timeOut = Math.random() * 2;
setTimeout(function () {
if (timeOut < 1) {
resolve('200 OK');
} else {
reject('error: ' + timeOut);
}
}, timeOut * 1000);
}).then(function (r) {
log('Done: ' + r);
}).catch(function (reason) {
log('Failed: ' + reason);
});
Promise的参数为一个方法有两个参数:resolve
和reject
// 0.5秒后返回输入相乘的计算结果:
function multiply(input) {
return new Promise(function (resolve, reject) {
log('calculating ' + input + ' x ' + input + '...');
setTimeout(resolve, 500, input * input);
});
}
// 0.5秒后返回输入相加的计算结果:
function add(input) {
return new Promise(function (resolve, reject) {
log('calculating ' + input + ' + ' + input + '...');
setTimeout(resolve, 500, input + input);
});
}
var p = new Promise(function (resolve, reject) {
log('start new Promise...');
resolve(2);
});
p.then(multiply)
.then(add)
.then(multiply)
.then(function (result) {
log('Got value: ' + result);
});
补充一点
setTimeout(resolve, 500, input);
相当于
setTimeout(function(){
resolve(input);
}, 500);
总结
链式调用的基础就是所有的方法的返回还是Promise对象
试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()
实现
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()
实现:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
由于p1
执行较快,Promise的then()
将获得结果'P1'
。p2
仍在继续执行,但执行结果将被丢弃。
如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。
详情:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject
Promise.reject(reason)
方法返回一个带有拒绝原因reason参数的Promise对象。
Promise.reject("Testing static reject").then(function(reason) {
// 未被调用
}, function(reason) {
console.log(reason); // "Testing static reject"
});
Promise.reject(new Error("fail")).then(function(result) {
// 未被调用
}, function(error) {
console.log(error); // stacktrace
});
详情:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
Promise.resolve(value)
方法返回一个以给定值解析后的Promise
对象。
value
可传的值
Promise
对象解析的参数。Promise
对象,thenable
。如果该值为promise,返回这个promise;
如果这个值是thenable(即带有"then" 方法
)),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;
否则返回的promise将以此值完成。
此函数将类promise对象的多层嵌套展平。
示例
var promise1 = Promise.resolve(123);
promise1.then(function(value) {
console.log(value);
// expected output: 123
});
示例
var original = Promise.resolve(33);
var cast = Promise.resolve(original);
cast.then(function(value) {
console.log('value: ' + value);
});
console.log('original === cast ? ' + (original === cast));
/*
* 打印顺序如下,这里有一个同步异步先后执行的区别
* original === cast ? true
* value: 33
*/
日志顺序颠倒其实是由于异步地调用then
方法。
不要在解析为自身的thenable 上调用
Promise.resolve
,这将导致无限递归,因为它试图展平无限嵌套的promise。
示例
// Resolve一个thenable对象
var p1 = Promise.resolve({
then: function(onFulfill, onReject) { onFulfill("fulfilled!"); }
});
console.log(p1 instanceof Promise) // true, 这是一个Promise对象
p1.then(function(v) {
console.log(v); // 输出"fulfilled!"
}, function(e) {
// 不会被调用
});
// Thenable在callback之前抛出异常
// Promise rejects
var thenable = {
then: function(resolve) {
throw new TypeError("Throwing");
resolve("Resolving");
}
};
var p2 = Promise.resolve(thenable);
p2.then(function(v) {
// 不会被调用
}, function(e) {
console.log(e); // TypeError: Throwing
});
// Thenable在callback之后抛出异常
// Promise resolves
var thenable = { then: function(resolve) {
resolve("Resolving");
throw new TypeError("Throwing");
}};
var p3 = Promise.resolve(thenable);
p3.then(function(v) {
console.log(v); // 输出"Resolving"
}, function(e) {
// 不会被调用
});
当调用一个 async 函数时,会返回一个 Promise 对象。根据mdn的解释
注意:
await
关键字仅仅在async function
中有效
例如:
async function testAsync() {
return "hello async";
}
let data = testAsync().then( (data) => {
console.log(data) // hello async
return data
});
console.log(data);
如果 async 函数没有返回值,又怎么样呢?很容易想到,它会返回 Promise.resolve(undefined)。
联想一下 Promise 的特点无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。
MDN的描述:
await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。 若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行async function。 若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。 另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。
或者可以这样简单理解
await
只能在async function
中使用。await
把它后面的异步方法变成了同步方法,resolve函数参数作为await
表达式的值。await
后的方法异常会抛出,所以外层要try/catch
。
async/await 相比原来的Promise的优势在于处理 then 链,不必把回调嵌套在then中,只要await即可,如
function say() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`说话`);
}, 1000);
});
}
function sing() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`唱歌`);
}, 1000);
});
}
async function mytest() {
try {
const v = await say();
const s = await sing();
console.log(v); // 说话
console.log(s) // 唱歌
} catch (e) {
console.log(e)
}
}
mytest();