JS中的异步“承诺”——Promise


最近在复习JS的知识,其中JS中的异步是最频繁被提及的,决定结合现阶段的学习来简单地梳理一下个人理解💭

JS 是一种典型的单线程语言,这是由它在浏览器上运行的性质所决定的。若在并行的线程中实现,并行的线程上无法实现对DOM的修改,此外发生重定向到另一个URL是非常危险的。这时候就有一个不容小觑的问题,JS在单线程上运行时,尤其是处理向服务器发出请求这种较慢的进程时,会使得浏览器处于漫长的等待响应中而被锁定🔒

解决这种问题的方式就是————异步

在js中,实现异步的方式主要有两种💕一是回调函数,二是Promise实现(ES6及之后)

一、使用回调函数实现异步

使用回调函数callback处理的时候,callback作为参数传递给任何异步函数,来告知在结果准备好时调用另一个函数。

    doSomethingAsync(callback);    // doSometingAsync接收回调函数作为参数(只传递引用,几乎无开销) 
    console.log('complete');
    
    // 当doSomethingAsync完成时,回调
    function callback(error => {
       if(!error)
           console.log('doSomethingAsync finished');
    });

✔️Terminal 的结果是:

complete
doSomethingAsync finished

当我们想串行执行几个异步任务的时候,往往会想到使用嵌套回调函数来实现。事实上,当我们在js中使用大量异步回调时,容易造成“回调地狱”问题,使得错误处理逻辑混乱并且代码阅读困难。解决回调地狱,可以使用Promise +Generator + Async / Await

二、使用ES6中的Promise实现异步

单从字面意义出发,我想理解为Promise就是JavaScript对于用户请求的数据返回承诺。Promise不仅是实现异步,还可以解决因为大量回调串行造成的回调地狱。在ES6中依旧可以使用回调,但是Promise提供了更加去清洗的语法chains异步命令,因此可以串行运行。

Promise的三种状态: pending(等待) 、resolved/fulfilled(已完成) 、rejected(已拒绝)。状态的转换只可以是pending → fulfilled / rejected

Promise构造函数的两个参数:

  • resolve: 处理成功之完成之后运行的回调函数 pending → fulfilled
  • reject:发生故障时运行的可选回调函数 pending → rejected
    两者都可以传入任意类型的值作为实参,表示Promise对象成功/失败的值

Promise常用的三种方法有:

  • then——异步执行成功之后的数据状态变为 resolve
  • catch——异步执行失败之后的数据状态变为reject
  • all——多个没有关系的 Promise 封装成为一个 Promise对象,使用then返回一个数组数据
  • ES2018引入了.finally方法,无论如何都会运行的逻辑,目前仅支持Chrome和Firebox

1. then方法——顺序的异步

promise.then(onFulfilled, onRejected); // 均为可选参数

异步链 Promise Chains 的实现

promise.then(onFulfilled1, onRejected1).then(onFulfilled2,onRejected2);

举个🌰(两个Promise实现3s后打印)

let promiseA = new Promise((reslove,reject) => {
    setTimeout(() => {
      resolve('success')
    }, 1000)
})
promiseB = promiseA.then(res => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('PromiseB 中返回一个Promise对象')
      },2000)
    })
})
promiseB.then(res => {
    console.log(res)  // 3s之后打印“PromiseB ... ” 
})

再举个坏🌰(抛异常):

const db = require('dataset')

//connect to the database
function asyncDBconnect(param){
    return new Promise((resolve, reject) => {
      db.connect(param, (err, connection) => {
        if(err)
          reject(err); //连接失败,处理异常
        else
          resolve(connection);
      });
    });
}

数据库API提供一个接收回调函数的connect()方法;
外部的asyncDBcontent()函数立刻返回一个新的Promise,并在简历之前的连接或者失败之后运行resolve或者reject
2.all 方法——多个异步调用,同时启动,无关顺序

Promise.all([async1, async2, async3])
    .then(values => {
      console.log(values);
      return values;
    })
    .catch(err => {
      console.log('error',err);
    })

在上述例子中,接收一组函数并且返回另外一个Promise,在all中的所有异步操作结束之后才能回调。
特点:如果任何一个异步函数调用失败,则Promise.all()立即终止
3.race 方法——多个异步调用,仅最快的Promise才能完成
与all()类似,race()也是多个异步的调用,但是不同的是他会在第一个Promise对象 resolve或者reject 之后立即执行回调(解析或拒绝),不用等待全部的Promise 执行完成。

Promise.race([async1, async2, async3])
    .then(value => {
      console.log(value);
      return value;
    })
    .catch(err => {
      console.log('error', err);
    })

除了上述三种之外, reject()、 catch()的用法也就不做过多的赘述。值得一提的是,当Promise 状态变为rejected的时候会被reject()、catch()捕捉到,而当.catch()前面已经设置了reject的回调函数,.catch()不会捕捉到状态变为rejected的情况。catch还可以捕捉到在resolve 或者 reject 中发生的错误。

Promise还可以在回调函数中使用return 和 throw, 所以在then 中可以return 出一个Promise对象或者其他值,也可以throw一个错误对象,但是如果没有return,将默认返回undefined, 那么后面的then中的回调函数接收到的也将是undefined.

本文主要讲Promise之后,会继续写关于使用Generator 和 Async/Await 解决回调地狱~ 敬请期待hhhhh

⭐️最后,附上最近看到的一道题:📝如何实现sleep的效果?
1、while循环的方式

function sleep(time){
  var start = Date.now();
  while(Date.now() < start +time){};
  console.log('sleep-1,休眠结束!');
  return;
}

缺点: 容易造成死循环
2、Promise的方式

function sleep(time){
  var temp = new Promise( (reslove) = > {
    console.log('sleep2-1')
    setTimeout(resolve, time)
  });
  return temp;
}
sleep(500).then(function(){
  console.log('sleep2-2') // 延迟500ms输出
})

3、async封装的方式

function sleep(time){
  return new Promise((resolve) => {
    setTimeout(resolve,time)
  });
}
async function test(){
  var temp = await sleep(1000);
  console.log('sleep-3');
  return temp;
}
test(); // 延迟1000ms之后输出了‘sleep-3’

4、generate的方式

function* sleep(time){
  yield new Promise(function(resolve,reject){
    console.log('sleep-4:1');
    setTimeout(resolve, time);
  })
}
sleep(500).next().value.then(function(){
  console.log('sleep-4:2'); //500ms之后输出
})

sleep写多了,cc成功地困了💤,晚安~ 😴


Author: Casey Lu
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Casey Lu !
评论
 Previous
前端性能优化的思考 前端性能优化的思考
前端性能的优化一直是前端开发之中的重中之重,技术层出不穷的今日,用户对页面访问速度和响应度的要求也与日俱增。在实际的项目开发中,用户体验就是前端优化的生产力,是前端开发的终极奥义。而要探讨性能优化的时候,往往是多方面的~ 首先回顾一下,有关
2020-03-03
Next 
四年一遇的日子,我的Blog上线了 四年一遇的日子,我的Blog上线了
思考了关于很多个人将来的打算,现阶段处于知识储备阶段,对于前端技术的内容虽然有了解和基础,仍然需要深度进阶。今天看了很多大牛的博客,尤其是冴羽大大的还有小伍学姐的Blog,感触很深。优秀的人总是会始终坚持着自己的习惯。 不断学习,不断进步。
2020-02-29
  TOC