作者:胡籽@毛豆前端
Promise Introduction
首先让我们来了解一下什么是Promise。
Promise是抽象异步处理对象以及对其进行各种操作的组件。
Promise对象有以下两个特点:
(1)对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:Pending
(进行中)、Resolved/Fulfilled
(已完成)、Rejected
(已失败)。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都是无法改变这个状态。
(2)一旦状态改变,就不会变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从Pending
变为Resolved
或者从Pending
变为Rejected
。
只要这两种情况发生,状态就凝固了,不会更改,一直保持结果。就算改变已发生,再对Promise
对象添加回调函数,也可立即得到这个结果。
Promise
对象可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,还提供了统一的接口,更易控制异步操作。
Promise
存在的缺点:
(1) 一旦新建就会立即执行,无法中途取消;
(2) 如不设置回调函数,
Promise
内部抛出的错误,不会反应到外部;
(3) 当处于
Pending
状态,无法得知目前进展到哪个阶段(是刚开始还是即将完成)
Promise API
Constructor
Promise
类似 XMLHttpRequest
,从构造函数Promise
来创建新promise
对象作为接口。
可使用new
来调用 Promise
的构造器进行实例化,创建 promise
对象。
let promise = new Promise((resolve, reject) => {
// 异步处理
// 处理结束后,调用resolve 或 reject
})
Instance Method
可通过promise.then()
实例方法,为通过new
生成的promise对象
设置其值在resolve(成功)
或 reject(失败)
时调用的回调函数。
promise.then(onFulfilled, onRejected)
onFulfilled
、onRejected
两个都为可选参数。
只对异常情况做处理,可使用promise.then(undefined,onRejected)
, 只指定reject时的回调函数即可。异常处理promise.catch(onRejected)
可为更好的选择。
Static Method
Promise包括几个常用的静态方法:Promise.resolve()
、Promise.reject()
、Promise.all()
、Promise.race()
首先看一下Promise workflow
示例代码:
function asyncFunc() {
//new Promise之后,返回一个promise对象
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello Promise!')
}, 10)
})
}
//为promise对象设置.then() 调用返回值时的回调函数
asyncFunc().then((value) => {
console.log(value);
}).catch((error) => {
console.log(error);
})
asyncFunc()
返回的promise
对象会在setTimeout
之后的10ms时被resolve,此时then
的回调函数会被调用,并输出Hello Promise!
。当前情况下,catch
的回调函数不会被执行(因为promise返回了resolve)。
编写Promise代码
创建promise对象的流程如下:
(1)
new Promise(fn)
返回一个promise对象
(2) 在
fn
中指定异步操作处理结果正常:调用`resolve(处理结果值) 处理结果错误:调用reject(Error 对象)
接下来,按照上述流程用Promise来通过异步处理方式获取XMLHttpRequest(XHR)的数据。
创建一个Promise把XHR处理包装好的名为 getUrl
的函数
function getURL(URL) {
return new Promise((resolve, reject) => {
let req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = () => {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = () => {
reject(new Error(req.statusText));
};
req.send();
});
}
// 运行示例
let url = "http://httpbin.org/get";
getURL(url).then((value) => {
console.log(value);
}).catch((error) => {
console.error(error);
});
getURL
只有通过XHR取得结果状态为200时调用 resolve
(数据获取成功时), 其他情况则调用 reject
方法。
Promise源码
Promise.resolve
静态方法Promise.resolve
有:Promise.resolve(value)
、Promise.resolve(promise)
、Promise.resolve(theanable)
这三种形式。
都会产生一个新的 Promise
, 从而可以继续 then
链式调用。
Promise.resolve = function (value) {
return new Promise(function (resolve) {
resolve(value);
});
};
Promise.resolve
的实现就是new一个新的Promise实例并调用resolve方法,最后返回。
Example:
Promise.resolve('hello').then((value) => {
console.log(value); // 'hello'
}, (value) => {
// no called
})
var p = Promise.resolve([1, 2, 3]);
p.then((val) => {
console.log(val); //[1, 2, 3]
})
var old = Promise.resolve(true);
var new = Promise.resolve(old);
new.then((val) => {
console.log(val); // true
})
Promise.reject
Promise.reject
与 Promise.resolve
同理,只是替换成reject。
Promise.reject = function(value) {
return new Promise(function(resolve, reject) {
reject(value);
})
}
Promise.all
Promise.all
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
实现的思路就是类似与用一个计数器,从零开始,每当一个 Promise 实例调用了 resolve ,则 +1。当计数器等于 Promise 实例的数量时,表示全部执行完,此时返回结果。
Promise.all = function (arr) {
var args = Array.prototype.slice.call(arr);
return new Promise(function (resolve, reject) {
if(args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
if(val && (typeof val === 'object' || typeof val === 'function')){
var then = val.then;
if(typeof then === 'function'){
var p = new Promise(then.bind(val));
p.then(function(val) {
res(i, val);
}, reject)
return;
}
args[i] = val;
if(--remaining === 0){
resolve(args);
}
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
})
}
源码中是做减法,跟计数器做加法的思路是一致的。new 一个新的 Promise,当触发了计数器设定的值(即 0),则调用它的 resolve,从而触发 then 函数。
res 函数里,给每一个 Promise 实例绑定一个 then 方法,当触发 resolve,即触发 then,从而再次调用 res 函数。当传入的值不再是 Promise 实例,就用 args 记录,作为返回的结果数组。并重新设置计数器 remaining(做减法)。
当 remaining 被减到 0,表示所有传入的 Promise 实例都执行了 resolve,此时可以调用新 new 出来的 Promise 实例的 resolve 。
Example:
var promise = Promise.resolve(3);
Promise.all([true, promise]).then(values => {
console.log(values); // [true, 3]
});
Promise.race
Promise.race
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
与 Promise.all
不同的是:只要有一个 promise 对象进入 FulFilled
或者 Rejected
状态的话,就会继续进行后面的处理。
Promise.race = function (values) {
return new Promise(function (resolve, reject) {
values.forEach(function (value) {
Promise.resolve(value).then(resolve, reject);
})
})
}
最后
把Promise 从头捋了一遍,发现源码并非想象中的那般难。。。。