最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • node.js异步和Promise

    正文概述 掘金(秃头了更帅的金水)   2020-12-29   537

    Node.js的非阻塞I/O

    定义:I/O即Input/Output,一个系统的输入和输出,对外系统信息的一个交换; 可以比较为说话和听别人说话,说话就是输出(output),听就是输入(input) 阻塞I/O和非阻塞I/O的区别就是在于系统接受输入再到输出期间,能不能接受其他输入;

    例子:吃饭

    • 去食堂吃饭:排队打饭,等前面的人打完饭了,轮到你打饭,等你打完饭然后开始吃饭(阻塞I/O)
    • 去餐厅点菜:坐下,点菜,服务生招呼完你之后又去招呼别人,这期间你可以干自己的事,等厨房把你的菜做好然后端给你吃(非阻塞I/O)

    食堂阿姨和服务生=系统,输入=点菜,输出=端菜 食堂阿姨只能一份一份的打菜,你向食堂阿姨点菜(输入),阿姨给你打菜(输出),这就是阻塞I/O,给你打菜期间不能别人点菜; 服务生点完菜之后可以服务其他人,这就是非阻塞I/O,在你的菜好端给你(输出)之前可以接受其他人的点菜(输入)

    要理解非阻塞I/O要先确定系统边界,上面那个例子如果把厨师看作系统怎么都不能理解非阻塞I/O;

    node.js正常情况下所有的i/o操作都是非阻塞的,它会把大量计算能力分发到其他的C++线程,等其他C++线程计算完毕之后再把计算结果回调给nodejs线程,nodejs在把这个线程的结果返回给你的应用程序

    //非阻塞IO
    //第二个参数传入一个回调函数,执行中可以计算1+1
    var result = null;
    glob(__dirname + '/**/*', function (err, res) {
      result = res;
      console.log(result);
    });
    console.log(1 + 1);
    

    Callback回调函数

    nodejs有大量的非阻塞I/O,这些非阻塞I/O程序的运行结果是需要通过回调函数来获取的,通过回调函数来编程的方式就是异步编程

    callback函数要遵循一个规则,第一个参数是error,后面的参数才是结果

    function interview(callback) {
      setTimeout(() => {
        if (Math.random() < 0.8) {
          callback('success');
        } else {
          throw new Error('fail');
        }
      }, 500);
    }
    try {
      interview(function (val) {
        console.log(val);
      });
    } catch (e) {
      console.log('error', e);
    }
    

    上面这段代码回调函数throw error并不会被try catch捕获到,而是直接抛给nodejs全局 node.js异步和Promise 为什么会这样子呢,因为try catch调用栈是很有关系的,代码会存在函数调用函数的情况的,每个函数调用其他函数的时候会在调用栈里加一层,一层层累加形成一个调用的链条体系,在程序里就形成了一个栈结构 try catch的机制就是,比如在调用栈的第五项加入一个try catch,栈顶元素抛出一个错误,这个错误会逐层往上抛,抛到第五项的时候会被try catch捕获到; 而上面那段代码的settimeout里面的函数是一个另外事件循环里面回调的,新的事件循环都是一个全新的调用栈,所throw new Error就会被抛到nodejs全局,所以在settimeout异步任务里throw new Error是不会被外面的try catch捕获到,所以错误也需要callback传送出去;

    function interview(callback) {
      setTimeout(() => {
        if (Math.random() < 0.8) {
          callback('success');
        } else {
          callback(new Error('fail'));
        }
      }, 500);
    }
    interview(function (res) {
      if (res instanceof Error) {
        return console.log('car');
      }
      console.log(res);
    });
    

    但是nodejs回调函数有很多参数,所以约定第一个参数为error。第二个第三个为回调的结果

    事件循环(event loop)

    事件循环例子 比如在一个餐厅,一个客人点了一个鱼香茄子,服务生告诉厨房做一个鱼香茄子,建立一个鱼香茄子的线程,这时候另外一个客人点了一个小炒肉,服务生告诉厨房做一个小炒肉,又来一个客人点了一个番茄炒蛋,服务生告诉厨房做一个番茄炒蛋;假设说鱼香茄子做好了,服务生就把鱼香茄子端到你面前,结束了这个线程,这时候番茄炒蛋也好了,服务生也把这个番茄炒蛋端出去,最后小炒肉做好了,餐厅的任务就完成了; 这就是事件循环的例子,是一个处理事件的循环,每当有事件不断进来的时候,一旦其中有事件做完了就会把事件所需要的数据回调出去;

    const eventLoop = {
      queue: [], //空队列
      loop() {
        while (this.queue.length) {
          var callback = this.queue.shift();
          callback();//调用栈的底部,这个回调之前的的代码全部都是c++
        }
        setTimeout(this.loop.bind(this), 50);
      },
      add(callback) {
        this.queue.push(callback);
      },
    };
    eventLoop.loop();
    setTimeout(() => {
      eventLoop.add(function () {
        console.log(1);
      });
    }, 500);
    
    setTimeout(() => {
      eventLoop.add(function () {
        console.log(2);
      });
    }, 800);
    

    每个事件循环都是一个全新的调用栈,调用栈的地步就是一个event loop的触发事件,可以理解为第一个callback()

    Promise

    当前的事件循环得不到的结果,在未来的事件循环会得到结果,它是一个状态机

    • pending
    • fulfilled/resolved(正确)
    • rejected(错误)
    var promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        // resolve(2);
        reject(new Error());
      }, 200);
    })
      .then((res) => {
        // console.log(res);
      })
      .catch((error) => {
        console.log(error);
      });
    
    console.log(promise);
    

    .then 和 .catch

    • resolved状态的Promise会回调后面的第一个.then
    • rejected状态的Promise会回调后面的第一个.catch
    • 任何一个rejected状态后面没有.catch的Promise,都会造成浏览器/node环境的全局错误

    Promise可以解决很多异步流程控制问题,上面callback的代码改写为promise

    var promise = interview();
    function interview() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (Math.random() < 0.8) {
            resolve('success');
          } else {
            reject(new Error());
          }
        }, 500);
      });
    }
    promise
      .then((res) => {
        console.log(res);
      })
      .catch((error) => {
        console.log(error);
      });
    

    执行then和catch会返回一个新的Promise,该Promise最终状态根据then和catch的回调函数的执行结果决定

    • 如果回调函数最终是throw,该promise是rejected状态
    var promise = interview();
    var promise2 = promise.then((res) => {
      throw new Error();
    });
    
    setTimeout(() => {
      console.log(promise);
      console.log(promise2);
    }, 800);
    function interview() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (Math.random() < 0.8) {
            resolve('success');
          } else {
            reject(new Error());
          }
        }, 500);
      });
    }
    

    node.js异步和Promise

    • 如果回调函数最终是return,该promise是resolved状态,即便在catch中return也是resolved

    node.js异步和Promise

    • 如果回调函数最终return了一个Promise,该Promise会和回调函数return的Promise保持一致,这样就可以在Promise的链式调用中串行的执行任务
    var promise = interview();
    var promise2 = promise.then((res) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('我接受');
        }, 400);
      });
    });
    
    //在800ms和1000ms的时候打印
    setTimeout(() => {
      console.log(promise);
      console.log(promise2);
    }, 800);
    setTimeout(() => {
      console.log(promise);
      console.log(promise2);
    }, 1000);
    
    
    function interview() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (Math.random() < 1) {
            resolve('success');
          } else {
            reject(new Error());
          }
        }, 500);
      });
    }
    

    node.js异步和Promise

    800ms时promise2的状态还在pending,打印的promise2的状态和reutn new promise的状态一致;

    所以链式调用就可以改写为

    var promise = interview(1)
      .then((res) => {
        //then里面return一个promise,后续的操作会等promise完成之后再继续
        return interview(2);
      })
      .then((res) => {
        return interview(3);
      })
      .then((res) => {
        console.log('我笑了');
      })
      .catch((error) => {
        console.log('我哭了' + error.round);
      });
    
    function interview(round) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (Math.random() > 0.3) {
            resolve('success');
          } else {
            var error = new Error('fail');
            error.round = round;
            reject(error);
          }
        }, 500);
      });
    }
    

    并发异步Promise.all([]) Promise.all([])接收一个数组,当数组里的promise全部是resolved状态时调用.then,只要有一个rejected就调用.catch

    Promise.all([interview('geekbang'), interview('tencent')])
      .then((res) => {
        console.log('我笑了');
      })
      .catch((error) => {
        console.log('我在' + error.name + '挂了');
      });
    function interview(name) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (Math.random() > 0.3) {
            resolve('success');
          } else {
            var error = new Error('fail');
            error.name = name;
            reject(error);
          }
        }, 500);
      });
    }
    

    起源地下载网 » node.js异步和Promise

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元