Web前端之路让前端飞程序员

Promise 系统学习

2017-07-12  本文已影响265人  simuty

目录

##第一部分: 主要对于JS单线程与异步的进一步巩固
##第二部分:主要对于Promise入门、进阶、进阶的展开

第一部分 金字塔渊源

1.1 单线程之囧

JS引擎是单线程的。这意味着在任何环境中,只有一段JS代码会被执行。当JS引擎开始执行一个函数(比如回调函数)时,它就会把这个函数执行完,也就是说只有执行完这段代码才会继续执行后面的代码。

尽可能快的进行轮询,如果事件队列中有代码需要执行,它会让JS引擎执行这段代码,然后移到下一个需要执行的代码,或者等待新的代码进来。

前提已经确定: 一个一个执行那么如何提高速度呢?总不可能像去银行排队等呀等的吧。。。。那么并发就闪亮登场了

1.2 翻身之---异步

先了解一些概念
并发: 多个任务在同一时间内【似乎同时】执行的。通过穿插地执行它们的每个原子操作。这也就很大程度上提升了速度。
如:小明,一堂课,左手可以玩手机、右手可以写作业、左耳可以听歌、右耳可以听课。。。。【小明同学、请给大家讲一下并发】答曰【一个我同一时间做这么多事,这就叫并发】【滚。。】

【并行】是啥?就是一个线程干一件事,同时开启多个线程

既然说了【并行】与【并发】,那么【同步】【异步】又是什么?

【同步】就比如:做饭的步骤:先烧开水-->然后洗菜-->然后放面条--。。。。如果这样的算术水平估计小学都难以毕业。【效率低下】;
【异步/事件驱动】设置一个任务后立即返回,然后加上一个监听,当任务结束的时候,就去调用监听。比如:烧水的时候同时洗菜,【耳朵】听着水是否烧开了,【如果开了】回来赶紧关火,然后在做类似的操作。不过除了【忙碌,诗和远方就真的成远方了。。。】

比如如下代码

var fs = require('fs');
fs.readFile('./test.txt', 'utf8', function (err, data) { 
  if (err) {
    return console.log(err);
  }
  console.log(data);
}); 

console.log("11111111");


# node fs7.js
11111111
hello, world!

回到异步

单线程和异步确实不能同时成为一个语言的特性。
js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。


js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,
这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器
和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,
甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,
浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。

刚才说到浏览器为网络请求这样的异步任务单独开了一个线程,那么问题来了,这些异步任务完成后,主线程怎么知道呢?
答案就是回调函数,整个程序是事件驱动的,每个事件都会绑定相应的回调函数,

image.png

我们把刚才了解的概念和图中做一个对应,上文中说到的浏览器为异步任务单独开辟的线程可以统一理解为WebAPIs,上文中说到的任务队列就是callback queue,我们所说的主线程就是有虚线组成的那一部分,堆(heap)和栈(stack)共同组成了js主线程,函数的执行就是通过进栈和出栈实现的,比如图中有一个foo()函数,主线程把它推入栈中,在执行函数体时,发现还需要执行上面的那几个函数,所以又把这几个函数推入栈中,等到函数执行完,就让函数出栈。等到stack清空时,说明一个任务已经执行完了,这时就会从callback queue中寻找下一个人任务推入栈中(这个寻找的过程,叫做event loop,因为它总是循环的查找任务队列里是否还有任务)。

当我们的程序需要大量I/O操作和用户请求时,js这个具备单线程,异步,事件驱动多种气质的语言是多么应景!相比于多线程语言,它不必耗费过多的系统开销,同时也不必把精力用于处理多线程管理,相比于同步执行的语言,宿主环境的异步和事件驱动机制又让它实现了非阻塞I/O

回调地狱。够头疼的。。。。

task1(function (value1) {
    task2(value1, function(value2) {
        task3(value2, function(value3) {
            task4(value3, function(value4) {
                // Do something with value4
                // ... more task ...
                // I am in the callback hell
            });
        });
    });
});

第二部分 一步步跳出地狱

2.1 promise

Promise出现在的并不晚,其最初是在 E语言中被提出,而ECMAScript 6中的Promise规范来源于Promises/A+社区。

Promise是一个【用于异步处理对象】,其中包含了对异步进行各种操作的组件。Promise把JavaScript中的异步处理对象和处理规则进行了规范化,并按照【统一的接口】来编写,使用规定方法之外的写法会出现错误。使用Promise规范处理异步编程,相对比较简单和易于理解。Promise迷你书下载

Promise对象已在ECMAScript 2015中做为JavaScript的标准内置对象提供,这个对象根据Promise A+规范实现,相对比较简单。

Promise的意义就在于 then 链式调用 ,它避免了异步函数之间的层层嵌套,将原来异步函数的 嵌套关系 转变为便于阅读和理解的 链式步骤关系 。

2.2 Promise基础

2.2.1 Promise 构造与基本属性

使用 new 来调用 Promise 的构造器来进行实例化一个对象。

// 异步处理

var promise = new Promise(function(resolve, reject) { 
// 调用resolve(成功) / reject(失败)
});


第一个例子[简单]

var promise = new Promise(function(resolve, reject) {
  // 异步处理
  // 处理结束后、调用resolve 或 reject
  // resolve('Async Hello world');
  reject('Async Hello world');
});

promise.then(function(result){
// 获取文件内容成功时的处理
  console.log(result);
}).catch(function(error){
// 获取文件内容失败时的处理
 console.log(error);
});

第二个例子【稍微难点】

function asyncFunction() {
//构造器来进行实例化一个对象
    return new Promise(function (resolve, reject) {
    //20毫秒后执行
      setTimeout(function () {
      //设置成功的
        resolve('Async Hello world');
      }, 20);
    });
}

console.log('1111111');
//获取promise对象,20毫秒时执行成功方法
//实例方法
//onFulfilled: resolve成功时调用
//onRejected: reject失败时调用
asyncFunction()
  .then(function onFulfilled(value){
    console.log(value);
  })
  .catch(function onRejected(error){
    console.error(error);
  });
console.log('22222222');

打印的结果

1111111
22222222
Async Hello world
2.2.2 Promise的状态
1. 【初始化状态】 "unresolved" - Pending
2. 【成功状态】"has-resolution" - Fulfilled;
3. 【失败状态】"has-resolution" - Fulfilled;

当promise的对象状态发生变化时,用** .then **来定义【只会】被调用一次的函数。

image.png

【来个稍微复杂一些的】

function getURL(URL) {
  //定义并返回去promise对象
  return new Promise(function (resolve, reject) {
    var req = new XMLHttpRequest(); 
    req.open('GET', URL, true); 
    //http请求做判断200、500、error
    //还是第一个例子的resolve与reject的赋值
    req.onload = function () {
      if (req.status === 200) { 
        resolve(req.responseText);
      } else {
        reject(new Error(req.statusText));
      } 
    };
    req.onerror = function () { reject(new Error(req.statusText));
    };
    req.send(); 
  });
}



// 运行示例
var URL = "http://httpbin.org/get"; 
//成功与失败,由上图得出只可能有一个,
getURL(URL)
.then(function onFulfilled(value){
  console.log(value); 
})
.catch(function onRejected(error){
  console.error(error); 
});

2.3 Promise加深一点

2.3.1 快捷构造--Promise.resolve/reject

结构体构建的Promise如下所示:

var promise = new Promise(function(resolve, reject) {
   resolve('hello');
});
promise.then(function(value){
  console.log(value);
}).catch(function(error){
  console.log(error);
})

静态方法 Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。

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

Promise.reject同样是近亲。

Promise.reject(new Error("BOOM!"))
  .catch(function(error){ 
    console.error(error);
  });
2.3.2 为何顺序是这样呢?

顺序已经标出来了

var promise = new Promise(function (resolve){ 
    console.log("inner promise"); // 1 resolve(42);
}); 
promise.then(function(value){
    console.log(value); // 3
});
console.log("outer promise"); // 2

由于JavaScript代码会按照文件的【从上到下】的顺序执行,所以最开始 <1> 会执行,然后是resolve(42)被执行。这时候 promise 对象的已经变为【确定状态】,FulFilled被设置为了【42】 。promise对象已经是【确定状态】,从程序上说对回调函数进行同步调用也是行得通的。Promise也会以【异步的方式】调用该回调函数,这是在Promise设计上的【规定方针】

2.3.3 不要对异步回调函数进行同步调用
• 绝对不能对异步回调函数(即使在数据已经就绪)进行同步调用。
• 如果对异步回调函数进行同步调用的话,处理顺序可能会与预期不 符,可能带来意料之外的后果。
• 对异步回调函数进行同步调用,还可能导致栈溢出或异常处理错乱等 问题。
• 如果想在将来某时刻调用异步回调函数的话,可以使用 setTimeout 等异步API。

2.3 链式调用Promise chain

2.3.1 promise链式调用基本
function taskA(){
  console.log('A');
  //模拟A函数出错,
  //B函数不会执行
  //finalTask亦然执行
  throw new Error('A 函数出错');
}
function taskB() {
  console.log('B');
}
//错误处理
function onRejected(error) {
  console.log(error);
}
//结束
function finalTask() {
  console.log('结束');
}

var promise = Promise.resolve();
promise
    .then(taskA)
    .then(taskB)
    .catch(onRejected)
    .then(finalTask)

结果如下

A
Error: A 函数出错
    at taskA (/Users/51testing/Desktop/Promise/promise4.js:4:9)
    at process._tickCallback (internal/process/next_tick.js:103:7)
    at Module.runMain (module.js:592:11)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)
    at bootstrap_node.js:509:3
结束

Task都是【相互独立的】,只是被简单调用而已。

image.png

问题:假如将调用【A、B】的顺序调换一下,打印结果是否和原来一样呢?

promise
    .then(taskB)
    .then(taskA)
    .catch(onRejected)
    .then(finalTask)
2.3.2 链式调用传参

在函数的最后加上一个return就可以了将数据传送到下个链中使用了。

//平方
function doubleUp(value) {
  return value * 2;
}
//➕1
function increment(value) {
  return value + 1;
}
function output(value) {
  console.log(value);
}
var promise = Promise.resolve(2);
promise
    .then(doubleUp)
    .then(increment)
    .then(output)
    .catch(function(error){
      console.error(error);
    })

【结果:5】

Promise#then 不仅仅是注册一个【回调函数】那么简单,它还会将回调函数的【返回值进行变换promise对象并返回】,实际上 【Promise#catch 】只是promise.then(undefined, onRejected); 方 法的一个别名而已。

【敲黑板--此处是考点】
每次调用then都会返回一个新创建的promise对象,最开始学习一门新语言时总会再看完文档有个【错觉】,这么easy,但写代码时,头都是大的。【为什么????】因为例子没有返回推敲。

以下执行的结果是多少?

var promise = new Promise(function (resolve) {
      resolve(100); 
    });
promise
    .then(function (value) { 
      return value * 2;
    });
promise
    .then(function (value) {
        return value * 2; 
      });
promise
    .then(function (value) { 
      console.log("1: " + value); 
})

【没错--100】,仔细看所有的操作都是基于【最初的】promise的,而不是链式调用的。

2.3.4 对比

如果理解了链式调用,那么下边两种写法的对比就很清晰了

function badAsyncCall() {
  var promise = Promise.resolve();
  promise.then(function() {
    // 任意处理
    return newVar;
  });
  return promise;
}

首先在 promise.then 中产生的异常不会被外部捕获,此外,也不能得到 then 的返回值,即使其有返回值。

这样才对

function anAsyncCall() {
  var promise = Promise.resolve();
  return promise.then(function() {
    // 任意处理
    return newVar;
  });
}

。。。

参考
JavaScript Promise迷你书
bluebird

更多精彩内容请关注“IT实战联盟”哦~~~


IT实战联盟.jpg
上一篇 下一篇

猜你喜欢

热点阅读