难以理解的Promise、await/async概念梳理-含实例

Promise的基本概念

Pomise是个对象,用来封装异步操作,让开发人员以更简洁明了的方式书写异步操作的处理方式。

定义Promise对象的时候,需要向它的构造函数传递一个函数,这个函数封装了异步操作,这个函数称为执行器函数。执行器函数接受系统定义的两个函数作为参数(resolvereject),new完成后,构造函数会立即调用执行器函数,同时系统会传入上述两个函数的地址,开发人员就可以在执行器函数内调用resolvereject函数。

每个Promise对象都有三种状态:pendingfullfilledrejectedPromise对象在刚构造完成的时候,处于pending状态。如上文所述,在执行器函数内,可以通过调用resolve或者reject函数来转换Promise对象的状态。当reject函数被调用,它会抛出异常,应该在代码中处理这个异常。

每个Promise对象的状态只能转换一次,完成后,这个对象的状态就凝固了,无法再转换。在Promise的执行器函数内,如果把resolvereject都呼叫了,或者多次调用他们,他们不会都生效,只会排在前面的那一个会生效,但不会阻塞后面的代码。意思是,resolvereject函数并不会终止执行器函数的执行,他们后面的代码仍然会执行。

Promise实例生成以后,可以用then方法传入resolved状态和rejected状态的回调函数。也就是说,Promise对象的状态发生变化后,这些回调函数会被系统调用,可以在这两个回调函数里得到转换状态之后的结果。

一般来说,调用resolvereject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolvereject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

 const p = new Promise(
    (resolve, reject) => {
        console.log('这是Promise执行器函数输出的内容。')
    }
 )
 // Promise的参数内含参数new
 Promise(
    (resolve,reject) => {
        // 异步操作代码本身放在这里,只发起异步操作,但不会处理返回的数据
    }).then(
        // 异步操作完成后的回调函数放在这里
		)
// 以上程序输出结果为:
这是Promise执行器函数输出的内容。
new Promise(
    (resolve,reject) => {
        console.log('开始异步操作')
        setTimeout(resolve('这是异步操作返回的数据'),1000)
    }).then(
    (res) => {console.log('异步操作:',res)})

// 上述程序输出结果为:
开始异步操作
异步操作:
这是异步操作返回的数据

另一种方法:

new Promise(
    (resolve,reject) => {
        console.log('开始异步操作')
        setTimeout(
        (x=10) => {
 //箭头函数使用了默认参数
            if(x>0)
                resolve(x)
            else
                reject('错误')
        },
        2000)
    }).then(
    (res) => console.log('异步操作完成,返回结果是:',res),
    (err) => console.log(err))

then方法

它的作用是为 Promise实例添加状态改变后的回调函数。then方法内的return语句,调用后就会返回的是一个新的Promise实例,js会自动转化为return new Promise
采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
Promise主要用来解决异步操作太多、回调函数过于复杂的问题,例如以下ajax操作:

$.ajax({
    url: 'http://www.xxxx.com/savedata',
    data: {name:'user1'},
    success: function(data){
        console.log('保存成功');
    },
    error:function(err){
        console.log(err);
    }
  }
)

可以使用Promise改写成:

function asycnSave(){
    const save = new Promise(
    (resolve,reject)=>{
        let url = 'http://www.xxx.com/savedata'; 
       data = {new: 'user1'};
        $.post(url,data);
    }
    );
    return save;
}
save().then(
    (data) => {
        console.log('保存成功')}
);

如果没有return 语句,下一个then的回调函数会不会得到一个resolved状态的Promise?
试验证明:会得到一个resolved状态的Promise,只是resolved值是undefined。

const p = new Promise((resolve,reject) => {
    console.log('执行器函数执行。')
    resolve('状态转化为resolved')
})

p.then(res => {
    console.log('成功状态的回调,输出resolved值:', res)
}).then(res => {
    console.log('第二个then的resolved回调:',res)
})
/* 运行结果
执行器函数执行。
成功状态的回调,输出resolved值: 状态转化为resolved
第二个then的resolved回调: undefined
*/

catch方法

Promise.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。如果异步操作抛出错误,状态就会变为rejected,系统就会调用catch()方法指定的回调函数来处理这个错误。另外,then()方法指定的回调函数,如果运行中抛出错误,也会被下一个catch()方法捕获,这点比较重要。

经试验,如果通过单独调用then方法传入了成功的回调函数,同时,而没有传入失败的回调函数,那么,即使后来补充定义了catch函数,js仍然会抛出一个错误。

const p = new Promise((resolve, reject) => {
    reject('发生错误')
})
// 单独调用then传递方法
p.then(res => {
    console.log(res)
})

p.catch(error => {
    console.log(error)
})

// 运行结果如下:
没有调用
node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "没有调用".] {        
  code: 'ERR_UNHANDLED_REJECTION'
}

Node.js v18.17.1

promise中抛出错误,与调用reject方法的效果一样,可以被catch方法捕捉到。

const promise = new Promise(function(resolve, reject) {
throw new Error('发生错误')
  });
  promise.catch(function(error) {
    console.log(error);
  });

一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。不会退出进程、终止脚本执行,例如以下示例,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。但是在node中,以下代码是会被终止的。

从以下这个实例也可以看出,如果在Promise内发生了其他错误,Promise对象会被设置为rejected状态,那么相应的错误处理函数会被调用。即使调用了resolve函数也没有用。

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  console.log('everything is great');
});

setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123

catch()方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()方法,以及处理错误:

const someAsyncThing = function() {
    return new Promise(function(resolve, reject) {
      // 下面一行会报错,因为x没有声明
      resolve(x + 2);
    });
  };
  
  someAsyncThing()
  .catch(function(error) {
      console.log('oh no', '发生错误');
      return Promise.reject('hello,又错了')
  })
  .then(function(e) {
    console.log(e);
  }).catch(e=>console.log(e));

// 运行结果:
oh no 发生错误
hello,又错了

catch/then内声明的函数按顺序执行,catch只会执行一次(如果定义多个catch的话)。

综合案例:链式调用的执行和参数传递问题

在链式调用中,如果对象的状态被resolve或者reject了,那么它后面所有的then方法内的相应回调函数都会被调用。

then方法内调用return语句,会像构造函数里的resolve或者reject函数一样,把结果传递给下一个then方法内的函数。请看以下示例:

console.log('本文件研究,是否没有主动调用构造函数的resolve和reject参数代表的函数,在后续的then和catch内就不会调用响应的函数')

new Promise((resolve, reject) => {
  console.log('【第1个例子。研究没有在构造函数内显示调用resolve和reject的情况】这里是Promise的构造函数。在这个例子里,声明了then函数和catch函数,但没有在构造函数内显示调用resolve和reject,所以then和catch不会被调用')
  setTimeout(() => {
    console.log('3秒后,第1个例子的异步操作完成')
  }, 3000)
}).then(
  (res) => {
    console.log('then函数得到了resolve的结果:', res)
  }
).catch((err) => { console.log('catch被调用,得到了reject的内容')})

new Promise((resolve, reject) => {
  console.log('【第2个例子。研究显示调用resolve的情况。结论是,如果没有显示调用resolve和reject函数,那么在下一步的then和cath函数内相应的函数不会被调用。】这里是Promise的构造函数。在这个例子里,声明了then函数和catch函数,而且在构造函数内显示调用了resolve,所以then的第一个默认函数被调用了,并得到了resolve输出的结果。但是在构造函数内没有调用reject,所以catch函数没有被调用。')
  setTimeout(() => {
    console.log('3秒后,第二个例子的异步操作完成')
    resolve('【第二个例子resolve的结果】')
  }, 3000)
}).then(
  (res) => {
    console.log('then函数得到了resolve的结果:', res)
  }
).catch((err) => { console.log('catch被调用,得到了reject的内容')})

new Promise((resolve, reject) => {
  console.log('【第3个例子。研究reject后,then函数内的第二个参数和catch函数的调用顺序。结论是,如果then的第二个参数函数被定义了,那么catch函数不会被调用。】这里是Promise的构造函数。在这个例子里,声明了then函数和catch函数,而且在构造函数内显示调用了resolve,所以then会被调用了,并得到了resolve输出的结果。但是在构造函数内没有调用reject,所以catch函数没有被调用。')
  setTimeout(() => {
    console.log('3秒后,第三个例子的异步操作完成')
    reject('【第三个例子reject的结果】')
  }, 3000)
}).then(
  (res) => {
    console.log('then函数得到了resolve的结果:', res)
  },
  (rej) => {
    console.log('then函数的第二个参数函数被调用,得到了reject的结果:', rej)
  }
).catch((err) => { console.log('catch被调用,得到了reject的内容:', err)})
console.warn('从上面可以看出,构造函数内的语句是立即执行的,而then和catch函数是异步的,要在立即执行函数后才会执行。')

以上示例输出以下结果:

本文件研究,是否没有主动调用构造函数的resolve和reject参数代表的函数,在后续的then和catch内就不会调用响应的函数
【第1个例子。研究没有在构造函数内显示调用resolve和reject的情况】这里是Promise的构造函数。在这个例子里,声明了then函数和catch函数,但没有在构造函数内显示调用resolve和
reject,所以then和catch不会被调用
【第2个例子。研究显示调用resolve的情况。结论是,如果没有显示调用resolve和reject函数,那么在下一步的then和cath函数内相应的函数不会被调用。】这里是Promise的构造函数。 
在这个例子里,声明了then函数和catch函数,而且在构造函数内显示调用了resolve,所以then的第一个默认函数被调用了,并得到了resolve输出的结果。但是在构造函数内没有调用reject,所以catch函数没有被调用。
【第3个例子。研究reject后,then函数内的第二个参数和catch函数的调用顺序。结论是,如果then的第二个参数函数被定义了,那么catch函数不会被调用。】这里是Promise的构造函数 
。在这个例子里,声明了then函数和catch函数,而且在构造函数内显示调用了resolve,所以then会被调用了,并得到了resolve输出的结果。但是在构造函数内没有调用reject,所以catch函数没有被调用。
从上面可以看出,构造函数内的语句是立即执行的,而then和catch函数是异步的,要在立即执行函数后才会执行。
3秒后,第1个例子的异步操作完成
3秒后,第二个例子的异步操作完成
then函数得到了resolve的结果: 【第二个例子resolve的结果】
3秒后,第三个例子的异步操作完成
then函数的第二个参数函数被调用,得到了reject的结果: 【第三个例子reject的结果】

sync 和 await 语法

async用法

async是一个函数修饰符,被async修饰的函数会返回一个Promise对象。因此对async函数返回值可以直接使用then方法传入有关回调函数。

返回resolved状态的Promise对象

async函数内执行return语句,会返回一个resolved状态的Promise对象,return后面的值就是resolved的值,可以在then传入的resolved回调函数中接收到该值。

async function myFunction() {
  return "Hello";
}

等价于:

async function myFunction() {
  return Promise.resolve("Hello");
}

即使没有明确执行return语句,async函数还是返回一个resolved状态的Promise对象,只不过返回值是undefined

f1 = async () => {
// async函数内没有return语句
}

f1().then(
    (res) => {
				// resolved状态的回调函数还是会被调用
        console.log('调用了resolved状态的回调函数:', res)
    },
    (rej) => {
        console.log('调用了rejected状态的回调函数:',rej)
    }
)

// 运行结果
// *调用了resolved状态的回调函数: undefined*

返回rejected状态的Promise对象

如果async函数抛出了错误,那么它将返回一个rejected状态的Promise对象,可以使用catch方法或者在then方法中传入回调函数处理。

f2 = async () => {
    throw new Error('错误')
}
f2().catch(
    rej => console.log(rej)
)

// 运行结果
// Error: 错误

await用法

await 是一个命令,只能放在async函数内。它的作用是等待异步操作的完成,从而返回该结果。它具有return语句的功能,只不过返回的功能更复杂。

await命令和async修饰符不一样,它不会将返回值包装成Promise对象。

如果跟在await 命令后面的是一个Promise对象,那么await命令可以得到该对象返回的值(resolvereject抛出的结果),且取到值后语句才会往下执行;就是说,await会阻塞后面的代码。

如果跟在await 命令后面的不是Promise对象,会直接返回这个值。

如果await后面的操作发生错误,则会抛出异常,或者说返回了一个rejected状态的Promise对象,则需要调用try/catch结构来处理错误。

f3 = async () => {
    return Promise.reject('发生错误')
		// 或者
		throw new Error('发生错误')
}

f4 = async () => {
    try { await f3() }
    catch (e) {
        console.log(e)
    }
}
f4()
// 运行结果
// 发生错误
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值