欢迎访问我的个人博客 晓东的博客
Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效,使得 Node.js 应用的越来越广泛;Node.js 是单线程的,所以需要按顺序执行异步逻辑时一般采用后续传递风格,也就是将后续逻辑封装在回调函数中作为起始函数的参数,逐层嵌套,这会导致所谓的 “回调地域”。这种情况下,Promise 等其他异步编程解决方案应运而生。
Promise是什么?
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,是抽象异步处理对象以及对其进行各种操作的组件。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
ES6 中新增的规范对象。
Promise 流程
示例代码
function asyncFunction() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('Async Hello world');
}, 16);
});
}
asyncFunction().then(function (value) {
console.log(value); // => 'Async Hello world'
}).catch(function (error) {
console.log(error);
});
- new Promise构造器之后,会返回一个promise对象
- 为 promise 对象用设置 .then 调用返回值时的回调函数。asyncFunction 这个函数会返回 promise 对象, 对于这个 promise 对象,我们调用它的 then 方法来设置 resolve 后的回调函数, catch 方法来设置发生错误时的回调函数。
- 该promise对象会在setTimeout之后的16ms时被resolve, 这时 then 的回调函数会被调用,并输出 ‘Async Hello world’ 。
- 在这种情况下 catch 的回调函数并不会被执行(因为promise返回了resolve), 不过如果运行环境没有提供 setTimeout 函数的话,那么上面代码在执行中就会产生异常,在 catch 中设置的回调函数就会被执行。
- 当然,像promise.then(onFulfilled, onRejected) 的方法声明一样, 如果不使用catch 方法只使用 then 方法的话,如下所示的代码也能完成相同的工作。代码如下:
asyncFunction().then(function (value) {
console.log(value);
}, function (error) {
console.log(error);
});
Promise 状态
promise对象有以下三个状态:
1. Fulfilled:resolve(成功)时。此时会调用 onFulfilled
2. Rejected:reject(失败)时。此时会调用 onRejected
3. Pending:既不是resolve也不是reject的状态。也就是promise对象刚被创建后的初始化状态等
说明
- promise对象的状态,从Pending转换为Fulfilled或Rejected之后, 这个promise对象的状态就不会再发生任何变化。也就是说,Promise与Event等不同,在.then 后执行的函数可以肯定地说只会被调用一次。
- 另外,Fulfilled和Rejected这两个中的任一状态都可以表示为Settled(不变的)。
- Settled:resolve(成功) 或 reject(失败)。
- 当promise的对象状态发生变化时,用.then 来定义只会被调用一次的函数。
Promise API
promise.then(onFulfilled, onRejected)
var promise = new Promise(function(resolve, reject){
resolve("传递给then的值");
});
promise.then(function (value) {
console.log(value);
}, function (error) {
console.error(error);
});
这段代码创建一个promise对象,定义了处理onFulfilled和onRejected的函数
(handler),然后返回这个promise对象。
这个promise对象会在变为resolve或者reject的时候分别调用相应注册的回调函数。
• 当handler返回一个正常值的时候,这个值会传递给promise对象的onFulfilled方法。
• 定义的handler中产生异常的时候,这个值则会传递给promise对象的onRejected方
法。
promise.catch(onRejected)
var promise = new Promise(function(resolve, reject){
resolve("传递给then的值");
});
promise.then(function (value) {
console.log(value);
}).catch(function (error) {
console.error(error);
});
这是一个等价于promise.then(undefined, onRejected) 的语法糖。
Promise.resolve
Promise.resolve(promise);
Promise.resolve(thenable);
Promise.resolve(object);
var taskName = "task 1"
asyncTask(taskName).then(function (value) {
console.log(value);
}).catch(function (error) {
console.error(error);
});
function asyncTask(name){
return Promise.resolve(name).then(function(value){
return "Done! "+ value;
});
}
根据接收到的参数不同,返回不同的promise对象。
虽然每种情况都会返回promise对象,但是大体来说主要分为下面3类。
1. 接收到promise对象参数的时候,返回的还是接收到的promise对象
2. 接收到thenable类型的对象的时候,返回一个新的promise对象,这个对象具有一个 then 方法
3. 接收的参数为其他类型的时候(包括 JavaScript 对象或 null 等),返回一个将该对象作为值的新 promise 对象
Promise.reject(object)
返回一个使用接收到的值进行了reject的新的promise对象。
而传给Promise.reject的值也应该是一个 Error 类型的对象。
另外,和 Promise.resolve不同的是,即使Promise.reject接收到的参数是一个promise对
象,该函数也还是会返回一个全新的promise对象。
Promise.all(promiseArray)
var p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
console.log(results); // [1, 2, 3]
});
生成并返回一个新的promise对象。
参数传递promise数组中所有的promise对象都变为resolve的时候,该方法才会返回, 新
创建的promise则会使用这些promise的值。
如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返
回一个reject的新的promise对象。
由于参数数组中的每个元素都是由 Promise.resolve 包装(wrap)的,所以Paomise.all
可以处理不同类型的promise对象。
Promise.race(promiseArray)
var p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
Promise.race([p1, p2, p3]).then(function (value) {
console.log(value); // 1
});
生成并返回一个新的promise对象。
**参数 promise 数组中的任何一个 promise 对象如果变为 resolve 或者 reject 的话, 该函数就
会返回**,并使用这个 promise 对象的值进行 resolve 或者 reject。
Promise.all() 和 Promise.race() 的区别
Promise.all()
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
var startDate = Date.now();
// 所有promise变为resolve后程序退出
Promise.all([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (values) {
console.log(Date.now() - startDate + 'ms');
// 約128ms
console.log(values); // [1,32,64,128]
});
Promise.race()
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (value) {
console.log(value); // => 1
});
附加方法
done()
Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
实现方法
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
// 抛出一个全局错误
setTimeout(() => { throw reason }, 0);
});
};
从上面代码可见,done方法的使用,可以像then方法那样用,提供Fulfilled和Rejected状态的回调函数,也可以不提供任何参数。但不管怎样,done都会捕捉到任何可能出现的错误,并向全局抛出。
finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
下面是一个例子,服务器使用Promise处理请求,然后使用finally方法关掉服务器。
server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);
实现方法
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
上面代码中,不管前面的Promise是fulfilled还是rejected,都会执行回调函数callback。
使用案例
异步加载图片
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
var image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
用Promise对象实现的Ajax操作
var getJSON = function(url) {
var promise = new Promise(function(resolve, reject){
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
jquery 中的 Ajax
// 常规写法
$.ajax({
url: '/api/test',
type: 'POST',
data: {...},
success: function(res) {
// dosomething
},
fail: function(res) {
// dosomething
},
complete: function() {
// dosomething
}
})
// 新的写法
$.ajax({
url: '/api/test',
type: 'POST',
...
})
.done(function(res) {
// success and do something
})
.fail(function(res) {
// fail and do something
})
.always(function() {})