当我们在执行异步任务的时候,往往会使用一个回调函数,它会在异步任务结束时被调用,它有一个共同点就是会在未来被执行。例如在node.js中异步读取文件:
fs.readFile('/etc/passwd', (err, data) => { if (err) throw err; console.log(data);});
又如我们写了个ajax请求函数:
// 简化版,请忽略细节const request = function(callback){ let xhr = new XMLHttpRequest(); // ..... xhr.onreadystatechange = function(){ callback(xhr) }}//调用request(function(xhr){ if (xhr.readyState === 4 && xhr.status === 200) { // .... } else { //.... } })
甚至是一个定时器任务:
const doSomethingLater = function(delay, callback){ setTimeout(callback, delay*1000)}// 调用doSomethingLater(1, function(){ // .....})
这样看使用回调的方式似乎没什么问题,但是当回调函数中又有异步任务时,就可能出现多层回调,也就是回调地狱的问题。多层回调降低了代码的可读性和可维护性。
简单来讲,Promise将异步任务包装成对象,将异步任务完成后要执行的函数传给then方法,通过resolve来调用该函数。如上面定时器任务可以改写成:
const doSomethingLater = function(delay){ return new Promise((resolve)=>{ setTimeout(()=>{ resolve() }, delay*1000) })}doSomethingLater(1) .then(()=>{ console.log('任务1') })
如果定时任务中又执行定时任务,就可以这样写,而不是再嵌套一层回调:
doSomethingLater(1) .then(() => { console.log('任务1') return doSomethingLater(1) }) .then(() => { console.log('任务2') })
Promise的作用:
就算你的代码原来没有异步操作:
Promise.resolve() .then(() => { console.log(1) })console.log(2)// 2// 1
这一点可以查一下事件循环相关知识
Promise.reject('error') .then(() => { }) .catch((err) => { console.log(err) })// 等价于Promise.reject('error') .then(() => { }) .then(null, (err) => { console.log(err) })// 或者Promise.reject('error') .then(() => { }, (err) => { console.log(err) })
其实catch只是个语义化的语法糖,我们也可以直接用then来处理错误。
then方法第一个参数和第二个参数(或catch的参数),只是调用条件不同。
Promise.resolve() .then(() => { return 1 }) .then((res) => { console.log(res) // 1 }) Promise.resolve() .then(() => { // 不返回什么 }) .then((res) => { console.log(res) // undefined,因为函数默认返回undefined })
如果是返回一个promise对象:
Promise.resolve() .then(() => { return new Promise((resolve) => { resolve(2) }) }) .then((res) => { console.log(res) // 2, 根据返回的那个promise对象的状态来 })
我们可以通过包装,使一个promise对象的最后状态始终是成功的:
例如:
const task = () => { return new Promise((resolve, reject) => { // .... })}task() .then((res) => { console.log(res) }) .catch((err) => { console.log(err) })
原本调用task函数时需要在外面加一层catch捕获错误,其实可以包装一下:
const task = () => { return new Promise((resolve, reject) => { // .... }) .then((res) => { return { status: 'success', value: res } }) .catch((err) => { return { status: 'fail', value: err } })}// 现在调用就会更简洁!task() .then((result) => { console.log(result) })
catch中报错也可以在后面继续捕获,因为catch也是返回promise对象嘛
Promise.reject('first error') .catch((err) => { console.log(err) throw new Error('second error') }) .then(null, (err) => { console.log(err) })
await使得异步代码更像是同步代码,对串行的异步调用写起来更自然。await后面跟一个值或promise对象,如果是一个值,那么直接返回。如果是一个promise对象,则接受promise对象成功后的返回值。或者在await后面调用一个async函数
const task = async () => { return new Promise((resolve, reject) => { resolve(1) })}const handle = async () => { let value = await task() console.log(value)}handle() // 1
使用await要注意错误的捕获,因为await只关心成功:
const task = async () => { return new Promise((resolve, reject) => { reject(1) })}const handle = async () => { let value = await task() console.log(value)}handle()
这样写会报错,所以要么在task函数中捕获错误,要么就在task调用时捕获,像这样:
const task = async () => { return new Promise((resolve, reject) => { reject(1) })}const handle = async () => { let value = await task().catch((err) => { console.log(err) }) console.log(value) // undefine, 因为错误处理函数没返回值,你也可以返回错误信息,这样就会通过value拿到}
需要注意的是,使用await也会使代码变成异步的:
const handle = async () => { let value = await 1 console.log(value)}handle()console.log(2)// 2// 1
完~
联系客服