Callback 异步操作
es5中处理异步的时候,容易出现回调地狱的现象如
function loadScript (src, callback) {
let script = document.createElement('script')
script.src = src
script.onload = () => callback(script)
document.head.append(script)
}
loadScript('1.js', function (error, script) {
if (error) {
handleError(error)
} else {
// ...
loadScript('2.js', function (error, script) {
if (error) {
handleError(error)
} else {
// ...
loadScript('3.js', function (error, script) {
if (error) {
handleError(error)
} else {
// ...加载所有脚本后继续 (*)
}
})
}
})
}
})
如果嵌套变多,代码层次就会变深,维护难度也随之增加,尤其是如果我们有一个不是 … 的真实代码,就会包含更多的循环,条件语句等。
这有时称为 “回调地狱” 或者“回调金字塔”。
Promise
将上面用callback来处理的问题转为用promise来处理
function loadScript (src) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
script.src = src
script.onload = () => resolve(script)
script.onerror = (err) => reject(err)
document.head.append(script)
})
}
loadScript('1.js')
.then(loadScript('2.js'), (err) => {
console.log(err)
})
.then(loadScript('3.js'), (err) => {
console.log(err)
})
- 基本语法
new Promise( function(resolve, reject) {…} );
通过创建 Promise 对象开启一个异步操作的过程,一般用几步完成多次异步操作:
- new Promise(fn) 返回一个Promise 对象
- 在fn 中指定异步等处理
- 处理结果正常的话,调用resolve(处理结果值)
- 处理结果错误的话,调用reject(Error对象)
- 场景--封装一个ajax方法
function getURL (URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest()
req.open('GET', URL, true)
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText)
} else {
reject(new Error(req.statusText))
}
}
req.onerror = function () {
reject(new Error(req.statusText))
}
req.send()
})
}
// 运行示例
var URL = 'http://httpbin.org/get'
getURL(URL).then(function onFulfilled (value) {
console.log(value)
}).catch(function onRejected (error) {
console.error(error)
})
注
Promise 内部是有状态的(pending、fulfilled、rejected),Promise 对象根据状态来确定执行哪个方法。Promise 在实例化的时候状态是默认 pending 的,当异步操作是完成的,状态会被修改为 fulfilled,如果异步操作遇到异常,状态会被修改为 rejected,
状态转化是单向的,不可逆转,已经确定的状态(fulfilled/rejected)无法转回初始状态(pending),而且只能是从 pending 到 fulfilled 或者 rejected
then方法
.then()是一种链式调用的方法
.then是promise对象原型上的方法
- 基本语法
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 方法。
回顾
function loadScript (src) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
script.src = src
script.onload = () => resolve(script)
script.onerror = (err) => reject(err)
document.head.append(script)
})
}
loadScript('1.js')
.then(loadScript('2.js'), (err) => {
console.log(err)
})
.then(loadScript('3.js'), (err) => {
console.log(err)
})
回顾这段代码,因为promise(onFulfilled,onRejected),它的onFulfilled和onRejected是函数,当不是函数的时候,会被忽略,返回一个空的promise对象.
但是如上例子中,为什么被忽略掉还能执行呢?
因为其中的loadScript(2.js)它会被当成是一个表达式,来求取其值,求取结果的时候,它就会自然地被执行,从而返回一个promise对象.
既然应该是函数,那么该例应该写成以下写法,不然则会埋坑
loadScript('1.js')
.then(()=> {
return loadScript('2.js')
}, (err) => {
console.log(err)
})
.then(()=> {
return loadScript('3.js')
}, (err) => {
console.log(err)
})
极简promise雏形
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
大致逻辑是这样的:
- 调用then方法,将想要在Promise异步操作成功时执行的回调放入callbacks队列,其实也就是注册回调函数,可以向观察者模式方向思考;
- 创建Promise实例时传入的函数会被赋予一个函数类型的参数,即resolve,它接收一个参数value,代表异步操作返回的结果,当一步操作执行成功后,用户会调用resolve方法,这时候其实真正执行的操作是将callbacks队列中的回调一一执行;
Resolve & Reject 异步操作
一般情况下我们都会使用 new Promise() 来创建 Promise 对象,但是除此之外我们也可以使用其他方法。
静态方法 Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。
比如 Promise.resolve(42) 可以认为是以下代码的语法糖。
new Promise(function (resolve) {
resolve(42)
})
在这段代码中的 resolve(42) 会让这个 Promise 对象立即进入确定(即resolved)状态,并将 42 传递给后面 then 里所指定的 onFulfilled 函数。
方法 Promise.resolve(value) 的返回值也是一个 Promise 对象,所以我们可以像下面那样接着对其返回值进行 .then 调用。
Promise.resolve(42).then(function (value) {
console.log(value)
})
Promise.reject(error) 是和 Promise.resolve(value) 类似的静态方法,是 new Promise() 方法的快捷方式。
比如 Promise.reject(new Error(“出错了”)) 就是下面代码的语法糖形式。
new Promise(function (resolve, reject) {
reject(new Error('出错了'))
})
这段代码的功能是调用该Promise 对象通过then指定的 onRejected 函数,并将错误(Error)对象传递给这个 onRejected 函数。
Catch 异步操作
捕获异常是程序质量保障最基本的要求,可以使用 Promise 对象的 catch 方法来捕获异步操作过程中出现的任何异常。
- 基本语法
p.catch(onRejected);
p.catch(function(reason) {
// rejection
});
以之前then的处理代码来看,对它进行修改,使用catch来处理出错
function loadScript (src) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
script.src = src
script.onload = () => resolve(script)
script.onerror = (err) => reject(err)
document.head.append(script)
})
}
loadScript('1.js')
.then(()=> {
return loadScript('2.js')
})
.then(()=> {
return loadScript('3.js')
})
.catch(err => {
console.log(err)
})
建议用 reject(new Error())的方式来触发错误,而不是使用throw new Error来触发
因为 throw 的方式并没有改变 Pronise 的状态
All 异步操作
- 基本语法
Promise.all(promiseArray);
Promise.all 生成并返回一个新的 Promise 对象,所以它可以使用 Promise 实例的所有方法。参数传递promise数组中所有的 Promise 对象都变为resolve的时候,该方法才会返回, 新创建的 Promise 则会使用这些 promise 的值。
如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的 Promise 对象。
由于参数数组中的每个元素都是由 Promise.resolve 包装(wrap)的,所以Promise.all 可以处理不同类型的 promose对象。
示例:
var p1 = Promise.resolve(1)
var p2 = Promise.resolve(2)
var p3 = Promise.resolve(3)
Promise.all([p1, p2, p3]).then((results) => {
console.log(results) // [1, 2, 3]
})
Race 异步操作
Promise.race 生成并返回一个新的 Promise 对象.
参数 promise 数组中的任何一个 Promise 对象如果变为 resolve 或者 reject 的话, 该函数就会返回,并使用这个 Promise 对象的值进行 resolve 或者 reject。
例:
var p1 = Promise.resolve(1)
var p2 = Promise.resolve(2)
var p3 = Promise.resolve(3)
Promise.race([p1, p2, p3]).then((value) => {
console.log(value) // 1
})