慕课网Promise学习(1)

课程来源:https://www.imooc.com/learn/949

1.Promise介绍

Promise是什么
1.主要用于异步计算
2.可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。
3.可以在对象之间传递和操作Promise,帮助我们处理队列

Promise产生的背景

根源是为了优化表单提交的用户体验,而开发了JavaScript这款包含大量异步操作的脚本语言。

在提交表单中异步程序的表现是怎么样的呢?就是当你注册会员的时候,填写了昵称这玩意,然后再填写密码的时候,同时服务器里会检测这个昵称是否已经被注册从而做出一些回应,而不用等你全部信息填写好点击提交才告诉你昵称已经存在。

借由异步的这一个特点,可以想到:异步操作能够避免界面冻结!异步的本质用大白话说就是:将耗时很长的A交付的工作交给系统之后,就去继续做B交付的工作。等到系统完成前面的工作之后,再通过回调或者事件,继续做A交付的剩下的工作。

从观察者的角度看起来,AB工作的完成顺序,和交付它们的时间顺序无关,所以叫“异步”。

异步操作的常用语法

1.事件侦听与响应

// 事件侦听与响应
document.getElementById('start').addEventListener('click', start, false);

function start() {
    // 响应事件,进行相应的操作
}

// jQuery 用 `.on()` 也是事件侦听
$('#start').on('click', start);

2.回调

// 回调
// 比较常见的有ajax
$.ajax('http://baidu.com', {
    success: function (res) {
        // 这里就是回调函数了
    }
});

// 或者在页面加载完毕后回调
$(function(){
    // 这里也是回调函数
});

JavaScript异步编程的4种方法:回调、事件监听、发布/订阅、Promise
具体可以参考:http://www.ruanyifeng.com/blog/2012/12/asynchronous_javascript.html

node.js 无阻塞和高并发,异步操作是其保障,大量操作依赖回调函数

以下才是Promise诞生的原因

1.解决因为异步操作所带来的回调地狱,从而导致维护性差,下面请看回调代码

a(function (resultsFromA) {
  b(resultsFromA, function (resultsFromB) {
    c(resultsFromB, function (resultsFromC) {
      d(resultsFromC, function (resultsFromD) {
        e(resultsFromD, function (resultsFromE) {
          f(resultsFromE, function (resultsFromF) {
            console.log(resultsFromF);
          })
        })
      })
    })
  })
});

总结就是曾经的异步操作依赖的回调函数中存在着“嵌套层次深,难以维护”、“无法正常使用return和throw”、“无法正常检索堆栈信息”和“多个回调之间难以建立联系”这四个主要问题需要被解决,于是Promise横空出世。

2.Promise入门

new Promise(
    /* 执行器 executor */
    function (resolve, reject) {
      // 一段耗时很长的异步操作
      resolve(); // 数据处理完成
      reject(); // 数据处理出错
    }
 ).then(function A() {
    // 成功,下一步
  }, function B() {
    // 失败,做相应处理
  });

Promise的概念和优点

【优点】
Promise是一个代理对象,它和原先要进行的操作并无关系
Promise通过引入一个回调,避免了更多的回调

【状态】
pending:待定,称为初始状态
fulfilled:实现,称为操作成功状态
rejected:被否决,称为操作失败状态

当Promise状态发生改变的时候,就会触发.then()里的响应函数来处理后续步骤
Promise状态已经改变,就不会再变

Promise实例一经创建,执行器立即执行。
在这里插入图片描述
一个简单实例:

// 简单的范例-定时执行
console.log("here we go");
new Promise(function(resolve,reject){
	setTimeout(function(){
		console.log("Make me confused");
	}, 2000); //定时函数设置好了后, 这个就算成功处理了数据

	resolve("bad "); //所以调用resolve, 把"bad"传出去 
	
})
.then(function(value){ //这个value接受到resolve传过来的"bad"
	console.log(value + "example")
});

//不使用
setTimeout(()=>{
	console.log("999");
	callback("success");
},1000)

function callback(data){
	console.log(data);
}

再看一个例子(两步执行):

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
    }, 2000);
  })
  .then(value => {
    console.log(value);
    return new Promise(resolve => {
      setTimeout(() => {
        resolve('world');
      }, 2000);
    });
  })
  .then(value => {
    console.log(value + ' world');
  });

这个范例主要是简单的演示了Promise如何解决回调地狱这个让人头大的问题。

一个Promise完成了,再.then()会怎样

console.log('start');
let promise = new Promise(resolve => {
  setTimeout(() => {
    console.log('the promise fulfilled');
    resolve('hello, world');
  }, 1000);
});

setTimeout(() => {
  promise.then(value => {
    console.log(value);
  });
}, 3000);

hello,world

讲师的原话:这段代码展示了Promise作为队列这个重要的特性,就是说我们在任何一个地方生成了一个Promise对象,都可以把它当做成一个变量传递到其他地方执行。不管Promise前面的状态到底有没有完成,队列都会按照固定的顺序去执行。

then()不返回Promise

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
    }, 2000);
  })
  .then(value => {
    console.log(value);
    console.log('everyone');
    (function () {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('Mr.Laurence');
          resolve('Merry Xmas');
        }, 2000);
      });
    }());
    return false;
  })
  .then(value => {
    console.log(value + ' world');
  });

我对以上代码的理解是这样的:最后一个then()方法里的value值代表的是上一个then()里的返回值,当没有return的时候,默认返回值为undefined。而resolve()里的数据为什么没被调用呢?因为上一个then()方法里return的是false而不是Promise实例。

要想调用resolve()里的数据,只要这么写就可以了

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
    }, 2000);
  })
  .then(value => {
    console.log(value);
    console.log('everyone');
    (function () {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('Mr.Laurence');
          resolve('Merry Xmas');
        }, 2000);
      });
    }()).then(value => {
      console.log(value + ' world');
    });
  })

3.then()

then()解析

then()接受两个函数作为参数,分别代表fulfilled和rejected
then()返回一个新的Promise实例,所以它可以链式调用
当前面的Promise状态改变时,then()根据其最终状态,选择特定的状态响应函数执行
状态响应函数可以返回新的Promise或其他值
如果返回新的Promise,那么下一级then()会在新的Promise状态改变之后执行
如果返回其他任何值,则会立刻执行下一级then()

看一个问题:
.then本来就会自动返回promise,为什么还有自己专门写return Promise呢?有什么实际用途吗?

解析:
then确实会返回一个promise,但是如果不手动return Promise,那么默认返回的promise状态就是resolved,值看你return的是啥了,不写return的话,值是undefined,return非promise,那么值就是这个非promise。而手动return Promise,那返回的promise状态就不一定是resolved了,因此就可以改变下一个then/catch调用的结果了。

then()的嵌套

then()里面有then()的情况:因为then()返回的还是Promise实例,故会等里面的then()执行完,再执行外面的,因此对于我们来说,此时最好将其展开,会更好的进行阅读。以下是then嵌套的代码

console.log('start');
new Promise(resolve => {
    console.log('Step 1');
    setTimeout(() => {
      resolve(100);
    }, 1000);
  })
  .then(value => {
    return new Promise(resolve => {
        console.log('Step 1-1');
        setTimeout(() => {
          resolve(110);
        }, 1000);
      })
      .then(value => {
        console.log('Step 1-2');
        return value;
      })
      .then(value => {
        console.log('Step 1-3');
        return value;
      });
  })
  .then(value => {
    console.log(value);
    console.log('Step 2');
  });

解套后的代码为:

console.log('start');
new Promise(resolve => {
    console.log('Step 1');
    setTimeout(() => {
      resolve(100);
    }, 1000);
  })
  .then(value => {
    return new Promise(resolve => {
      console.log('Step 1-1');
      setTimeout(() => {
        resolve(110);
      }, 1000);
    })
  })
  .then(value => {
    console.log('Step 1-2');
    return value;
  })
  .then(value => {
    console.log('Step 1-3');
    return value;
  })
  .then(value => {
    console.log(value);
    console.log('Step 2');
  });

嵌套的then并没有返回新的Promise实例只是传递resolve值(110),其执行顺序就像你写jquery链式调用一样。

Promise小测试

看以下代码进行分析四种Promise的区别是什么?是什么原因导致了不同的执行流程?

// 问题一
doSomething()
  .then(function () {
    return doSomethingElse();
  })
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(resultDoSomethingELlse)

// 问题二
doSomething()
  .then(function () {
    doSomethingElse();
  })
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(undefined)
//注意:doSomethingElse(undefined)和finalHandler(undefined)同时执行

// 问题三
doSomething()
  .then(doSomethingElse())
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(resultOfDoSomething)
//注意:doSomethingElse(undefined)和doSomething()同时执行


// 问题四
doSomething()
  .then(doSomethingElse)
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(resultOfDoSomething) ==> finalHandler(resultOfDoSomethingElse)

Promise错误处理

因为在这一块,讲师貌似犯了些小错误,很多人反应很强烈,至于这错误到底是不是错误我也不太懂,但是不能把有异议的内容也写进来吧,于是我在网上找了篇自己能理解的Promise错误处理,贴上来给大家看看。

谈到Promise错误处理,就要把reject拿出来晾一晾了。reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调(严格来说这不算是错误处理吧。。。),看下面的代码。

function getNumber() {
  var p = new Promise(function(resolve, reject) {
    //做一些异步操作
    setTimeout(function() {
      var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
      if(num <= 5) {
        resolve(num);
      } else {
        reject('数字太大了');
      }
    }, 2000);
  });
  return p;
}
getNumber().then(function(data) {
  console.log('resolved');
  console.log(data);
}, function(reason) {
  console.log('rejected');
  console.log(reason);
});

getNumber函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为失败的原因。

运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到“成功”和“失败”的两种结果。

另一种处理错误和异常的方法:catch。 其实它和上面then的第二个参数一样,用来指定reject的回调,用法是这样的:

function getNumber() {
  var p = new Promise(function(resolve, reject) {
    //做一些异步操作
    setTimeout(function() {
      var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
      if(num <= 5) {
        resolve(num);
      } else {
        reject('数字太大了');
      }
    }, 2000);
  });
  return p;
}
getNumber().then(function(data) {
  console.log('resolved');
  console.log(data);
}).catch(function(reason) {
  console.log('rejected');
  console.log(reason);
});

效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中,请看下面的代码,然后分别代入自行测试一下

// 测试代码1
getNumber().then(function(data) {
  console.log(name());
  console.log('resolved');
  console.log(data);
}, function(reason, data) {
  console.log('rejected');
  console.log(reason);
});
// 测试代码2
getNumber().then(function(data) {
  console.log(name());
  console.log('resolved');
  console.log(data);
}).catch(function(reason) {
  console.log('rejected');
  console.log(reason);
});

在resolve的回调中,我们console.log(name());而name()这个函数是没有被定义的。如果我们不用Promise中的 catch,代码运行到这里就直接在控制台报错了,不往下运行,但是使用catch就不同了。

也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

参考文章:
https://github.com/CruxF/Blog/issues/7
https://github.com/merrier/imooc-promise-sample

猜你喜欢

转载自blog.csdn.net/u014465934/article/details/89302264