面向面试题和实际使用谈promise

读完这篇文章,预计会消耗你 40 分钟的时间。

  “金三银四,金九银十”,都是要收获的季节。面对各种面试题,各种概念、原理都要去记,挺枯燥的。本文是面向面试题和实际使用谈一下Promise。

Ajax 出现的时候,刮来了一阵异步之风,现在 Nodejs
火爆,又一阵异步狂风刮了过来。需求是越来越苛刻,用户对性能的要求也是越来越高,随之而来的是页面异步操作指数般增长,如果不能恰当的控制代码逻辑,我们就会陷入无穷的回调地狱中。

Promise是什么?

  Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一。这句话说的很明白了,Promise是一种用于解决异步问题的思路、方案或者对象方式。在js中,经常使用异步的地方是Ajax交互。比如在es5时代,jQueryajax的使用success来完成异步的:

$.ajax({
   url:'/xxx',
   success:()=>{},
   error: ()=>{}
})

  这种方法可以清楚的让读代码的人明白那一部分是Ajax请求成功的回调函数和失败的回调函数。但是问题来了,当一次请求需要连续请求多个接口时,这段代码仿佛进入了一团乱麻中:

// 第一次 
$.ajax({
     url:'/xxx',
     success:()=>{
         // 第二次
         $.ajax({
             url:'/xxx',
             success:()=>{
               // 第三次
               $.ajax({
                  url:'/xxx',
                  success:()=>{
                   // 可能还会有
                  },
                  error: ()=>{}
                })
             },
             error: ()=>{}
        })
     },
     error: ()=>{}
}) 

  也许因为success和error这两个函数的存在,理解这段代码会很简单,但是当我们更改需求的时候,这将成为一个棘手的问题。这就是回调地狱。

  当然,这是es5时代。当js这门语言发展到es6时代时,Promise的出现给异步带来了变革。Promise提供一个then,来为异步提供回调函数:

$.ajax({
    url:'/xxx',
}).then( ()=>{
   // 成功的回调
}, ()=>{
  // 失败的回调 
})

  而其先进之处则是,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。

ECMAScript 6 已经将异步操作纳入了规范,现代浏览器也内置了 Promise
对象供我们进行异步编程,那么此刻,还在等啥?赶紧学习学习 Promise
的内部原理吧!

Promise的用法

  说完了Promise是什么,下面让我们研究一下Promise怎么使用。首先,Promise是一个对象,因此,我们使用new的方式新建一个。然后给它传一个函数作为参数,这个函数呢也有两个参数,一个叫resolve(决定),一个叫reject(拒绝),这两个参数也是函数。紧接着,我们使用then来调用这个Promise:

const fn = new Promise(function (resolve, reject) {
  setTimeout(()=>{
    let num = Math.ceil(Math.random() * 10) // 假设num为7
    if (num > 5) {
      resolve(num) //返回7
    } else {
      reject(num)
    }
  },2000)
})
fn.then((res)=>{
  console.log(res) // 7
},(err)=>{
  console.log(err)
})

  这就是最简单的Promise的使用。假设2秒钟之后生成随机数为7,因此resolve回调函数运行,then走第一个函数,console.log(7)。假设2秒钟之后生成随机数为3,因此reject回调函数运行,then走第二个函数,console.log(3)。

  那你可能说了,Promise要是就这点能耐也没什么大不了的啊?我们上面说了Promise的先进之处在于可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作:

fn = new Promise(function (resolve, reject) {
  let num = Math.ceil(Math.random() * 10)
  if (num > 5) {
    resolve(num)
  } else {
    reject(num)
  }
})
// 第一次回调
fn.then((res)=>{
  console.log(`res==>${res}`)
  return new Promise((resolve,reject)=>{
    if(2*res>15){
      resolve(2*res)
    }else{
      reject(2*res)
    }
  })
},(err)=>{
  console.log(`err==>${err}`)
}).then((res)=>{ // 第二次回调
  console.log(res)
},(err)=>{
  console.log(`err==>${err}`)
})

  这就可以代替了上面类似es5时代的jQurey的success的嵌套式的回调地狱的产生,让代码清爽了许多。这里的resolve就相当于以前的success。

第一章 了解 Promise

Promise的原理

  在Promise的内部,有一个状态管理器的存在,有三种状态:pending、fulfilled、rejected。

    (1) promise 对象初始化状态为 pending。

    (2) 当调用resolve(成功),会由pending => fulfilled。

    (3) 当调用reject(失败),会由pending => rejected。

  因此,看上面的的代码中的resolve(num)其实是将promise的状态由pending改为fulfilled,然后向then的成功回掉函数传值,reject反之。但是需要记住的是注意promsie状态
只能由 pending => fulfilled/rejected,
一旦修改就不能再变(记住,一定要记住,下面会考到)。

  当状态为fulfilled(rejected反之)时,then的成功回调函数会被调用,并接受上面传来的num,进而进行操作。promise.then方法每次调用,都返回一个新的promise对象
所以可以链式写法(无论resolve还是reject都是这样)。

一、场景再现

由于 javascript
的单线程性质,我们必须等待上一个事件执行完成才能处理下一步,如下:

// DOM ready之后执行
$(document).ready(function(){
    // 获取模板
    $.get(url, function(tpl){
        // 获取数据
        $.get(url2, function(data){
            // 构建 DOMString
            makeHtml(tpl, data, function(str){
                // 插入到 DOM 中
                $(obj).html(str);
            });
        });
    });
});

为了减少首屏数据的加载,我们将一些模板和所有数据都放在服务器端,当用户操作某个按钮时,需要将模板和数据拼接起来插入到
DOM 中,这个过程还必须在 DOMReady
之后才能执行。这种情况是十分常见的,如果异步操作再多一些,整个代码的缩进让人看着很不舒服,为了优雅地处理这个问题,ECMAScript
6 引入了 Promise 的概念,目前一些现代浏览器已经支持这些新东西了!

澳门新葡亰3522平台游戏,Promise的几种方法

二、模型

为了让代码流程更加清晰,我们假想着能够按照下面的流程来跑程序:

new Promise(ready).then(getTpl).then(getData).then(makeHtml).resolve();

先将要事务按照执行顺序依次 push 到事务队列中,push 完了之后再通过
resolve 函数启动整个流程。

整个流程的操作模型如下:

promise(ok).then(ok_1).then(ok_2).then(ok_3).reslove(value)------+
         |         |          |          |                       |
         |         |          |          |        +=======+      |
         |         |          |          |        |       |      |
         |         |          |          |        |       |      |
         +---------|----------|----------|--------→  ok() ←------+
                   |          |          |        |   ↓   |
                   |          |          |        |   ↓   |
                   +----------|----------|--------→ ok_1()|
                              |          |        |   ↓   |
                              |          |        |   ↓   |
                              +----------|--------→ ok_2()|
                                         |        |   ↓   |
                                         |        |   ↓   |
                                         +--------→ ok_3()-----+
                                                  |       |    |       
                                                  |       |    ↓
@ Created By Barret Lee                           +=======+   exit

在 resolve 之前,promise 的每一个 then 都会将回调函数压入队列,resolve
后,将 resolve
的值送给队列的第一个函数,第一个函数执行完毕后,将执行结果再送入下一个函数,依次执行完队列。一连串下来,一气呵成,没有丝毫间断。

then

  then方法用于注册当状态变为fulfilled或者reject时的回调函数:

// onFulfilled 是用来接收promise成功的值
// onRejected 是用来接收promise失败的原因
promise.then(onFulfilled, onRejected);

  需要注意的地方是then方法是异步执行的。

// resolve(成功) onFulfilled会被调用
const promise = new Promise((resolve, reject) => {
   resolve('fulfilled'); // 状态由 pending => fulfilled
});
promise.then(result => { // onFulfilled
    console.log(result); // 'fulfilled' 
}, reason => { // onRejected 不会被调用
})

// reject(失败) onRejected会被调用
const promise = new Promise((resolve, reject) => {
   reject('rejected'); // 状态由 pending => rejected
});
promise.then(result => { // onFulfilled 不会被调用
}, reason => { // onRejected 
    console.log(rejected); // 'rejected'
})

三、简单的封装

如果了解 Promise,可以移步下方,看看对 Promise 的封装:

Github: 
DEMO: 

如果还不是很了解,可以往下阅读全文,了解一二。

catch

  catch在链式写法中可以捕获前面then中发送的异常。

fn = new Promise(function (resolve, reject) {
  let num = Math.ceil(Math.random() * 10)
  if (num > 5) {
    resolve(num)
  } else {
    reject(num)
  }
})
fn..then((res)=>{
  console.log(res)
}).catch((err)=>{
  console.log(`err==>${err}`)
})

  其实,catch相当于then(null,onRejected),前者只是后者的语法糖而已。

第二章 Promise 原理

resolve、reject

  Promise.resolve 返回一个fulfilled状态的promise对象,Promise.reject
返回一个rejected状态的promise对象。

Promise.resolve('hello').then(function(value){
    console.log(value);
});

Promise.resolve('hello');
// 相当于
const promise = new Promise(resolve => {
   resolve('hello');
});

// reject反之