Javascript中隐藏大BOSS-Promise的使用方法

直接打印出来看看吧,console.dir(Promise),就这么简单粗暴。

这么一看就明白了,Promise是一个构造函数,自己身上有all、reject、resolve这几个眼熟的方法,原型上有then、catch等同样很眼熟的方法。这么说用Promise new出来的对象肯定就有then、catch方法喽,
 

new一个玩玩吧。

var p = new Promise(function(resolve, reject){
    //做一些异步操作
    setTimeout(function(){
        console.log('执行完成');
        resolve('随便什么数据');
    }, 2000);
});

Promise的构造函数接收一个参数,是函数,并且传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。其实这里用“成功”和“失败”来描述并不准确,按照标准来讲,resolve是将Promise的状态置为fullfiled,reject是将Promise的状态置为rejected。不过在我们开始阶段可以先这么理解,后面再细究概念。

在上面的代码中,我们执行了一个异步操作,也就是setTimeout,2秒后,输出“执行完成”,并且调用resolve方法。

运行代码,会在2秒后输出“执行完成”。注意!我只是new了一个对象,并没有调用它,我们传进去的函数就已经执行了,这是需要注意的一个细节。所以我们用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数,如:

function runAsync(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('执行完成');
            resolve('随便什么数据');
        }, 2000);
    });
    return p;            
}
runAsync()

这时候你应该有两个疑问:1.包装这么一个函数有毛线用?2.resolve(‘随便什么数据’);这是干毛的?

我们继续来讲。在我们包装好的函数最后,会return出Promise对象,也就是说,执行这个函数我们得到了一个Promise对象。还记得Promise对象上有then、catch方法吧?这就是强大之处了,看下面的代码:

runAsync().then(function(data){
    console.log(data);
    //后面可以用传过来的数据做些其他操作
    //......
});

在runAsync()的返回上直接调用then方法,then接收一个参数,是函数,并且会拿到我们在runAsync中调用resolve时传的的参数。运行这段代码,会在2秒后输出“执行完成”,紧接着输出“随便什么数据”。

这时候你应该有所领悟了,原来then里面的函数就跟我们平时的回调函数一个意思,能够在runAsync这个异步任务执行完成之后被执行。这就是Promise的作用了,简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。

你可能会不屑一顾,那么牛逼轰轰的Promise就这点能耐?我把回调函数封装一下,给runAsync传进去不也一样吗,就像这样:

function runAsync(callback){
    setTimeout(function(){
        console.log('执行完成');
        callback('随便什么数据');
    }, 2000);
}

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

效果也是一样的,还费劲用Promise干嘛。那么问题来了,有多层回调该怎么办?如果callback也是一个异步操作,而且执行完后也需要有相应的回调函数,该怎么办呢?总不能再定义一个callback2,然后给callback传进去吧。而Promise的优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。

~~~~~~~~~~~~~~~~~~

在过去的几年中,笔者看到了很多程序员在调用PouchDB或者其他promise化的API时遇到了很多困难。这让笔者认识到,在JavaScript程序员之中,只有少数人是真正理解了promise规范的。如果这个事实让你难以接受,那么思考一下我给出的一个题目:

Question:下面四个使用promise的语句之间的不同点在哪儿?

doSomething().then(function () {
return doSomethingElse();
});

doSomethin().then(functiuoin () {
doSomethingElse();
});

doSomething().then(doSomethingElse());

doSomething().then(doSomethingElse);

如果你知道这个问题的答案,那么恭喜你,你已经是一个promise大师并且可以直接关闭这个网页了。

但是对于不能回答这个问题的程序员中99.9%的人,别担心,你们不是少数派。没有人能够在笔者的tweet上完全正确的回答这个问题,而且对于第三条语句的最终答案也令我感到震惊,即便我是出题人。

答案在这篇博文的底部,但是首先,笔者必须先介绍为何promise显得难以理解,为什么我们当中无论是新手或者是很接近专家水准的人都有被promise折磨的经历。同时,笔者也会给出自认为能够快速、准确理解promise的方法。而且笔者确信读过这篇文章之后,理解promise不会那么难了。

在此之前,我们先了解一下有关promise的一些基本设定。

promise从哪里来?

如果你读过有关promise的文章,你会发现文章中一定会提到 回调深坑 ,不说别的,在视觉上,回调金字塔会让你的代码最终超过屏幕的宽度。

promise是能够解决这个问题的,但是它解决的问题不仅仅是缩进。在讨论到如何 解决回调金字塔问题 的时候,我们遇到真正的难题是回调函数剥夺了程序员使用return和throw的能力。而程序的执行流程的基础建立于一个函数在执行过程中调用另一个函数时产生的副作用。(译者注:个人对这里副作用的理解是,函数调用函数会产生函数调用栈,而回调函数是不运行在栈上的,因此不能使用return和throw)。

事实上,回调函数会做一些更邪恶的事情,它们剥夺我们在栈上执行代码的能力,而在其他语言当中,我们始终都能够在栈上执行代码。编写不在栈上运行的代码就像驾驶没有刹车的汽车一样,在你真正需要它之前,你是不会理解你有多需要它。

promise被设计为能够让我们重新使用那些编程语言的基本要素:return,throw,栈。在想要使用promise之前,我们首先要学会正确使用它。

~~~~

1. 异步回调 #

1.1 回调地狱 #

在需要多个操作的时候,会导致多个回调函数嵌套,导致代码不够直观,就是常说的回调地狱

1.2 并行结果 #

如果几个异步操作之间并没有前后顺序之分,但需要等多个异步操作都完成后才能执行后续的任务,无法实现并行节约时间

2. Promise #

Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果。
什么时候会用到过一段时间?答案是异步操作,异步是指可能比较长时间才有结果的才做,例如网络请求、读取本地文件等

3. Promise的三种状态 #

  • Pending Promise对象实例创建时候的初始状态
  • Fulfilled 可以理解为成功的状态
  • Rejected 可以理解为失败的状态

then 方法就是用来指定Promise 对象的状态改变时确定执行的操作,resolve 时执行第一个函数(onFulfilled),reject 时执行第二个函数(onRejected)

4. 构造一个Promise #

4.1 使用Promise #

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        if(Math.random()>0.5)
            resolve('This is resolve!');
        else
            reject('This is reject!');
    }, 1000);
});
promise.then(Fulfilled,Rejected)
  • 构造一个Promise实例需要给Promise构造函数传入一个函数。
  • 传入的函数需要有两个形参,两个形参都是function类型的参数。
    • 第一个形参运行后会让Promise实例处于resolve状态,所以我们一般给第一个形参命名为resolve,使 Promise 对象的状态改变成成功,同时传递一个参数用于后续成功后的操作
    • 第一个形参运行后会让Promise实例处于reject状态,所以我们一般给第一个形参命名为reject,将 Promise 对象的状态改变为失败,同时将错误的信息传递到后续错误处理的操作

4.2 es5模拟Promise #

function Promise(fn) {
    fn((data)=> {
        this.success(data);
    }, (error)=> {
        this.error();
    });
}

Promise.prototype.resolve = function (data) {
    this.success(data);
}

Promise.prototype.reject = function (error) {
    this.error(error);
}

Promise.prototype.then = function (success, error) {
    this.success = success;
    this.error = error;
}

4.3 es6模拟Promise #

class Promise {
    constructor(fn) {
        fn((data)=> {
            this.success(data);
        }, (error)=> {
            this.error();
        });
    }

    resolve(data) {
        this.success(data);
    }

    reject(error) {
        this.error(error);
    }

    then(success, error) {
        this.success = success;
        this.error = error;
        console.log(this);
    }
}

5. promise 做为函数的返回值 #

function ajaxPromise (queryUrl) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open('GET', queryUrl, true);
    xhr.send(null);
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.responseText);
        } else {
          reject(xhr.responseText);
        }
      }
    }
  });
}

ajaxPromise('http://www.baidu.com')
  .then((value) => {
    console.log(value);
  })
  .catch((err) => {
    console.error(err);
  });

6.promise的链式调用 #

  • 每次调用返回的都是一个新的Promise实例
  • 链式调用的参数通过返回值传递

then可以使用链式调用的写法原因在于,每一次执行该方法时总是会返回一个Promise对象

readFile('1.txt').then(function (data) {
    console.log(data);
    return data;
}).then(function (data) {
    console.log(data);
    return readFile(data);
}).then(function (data) {
    console.log(data);
}).catch(function(err){
 console.log(err);
});

7.promise API #

7.1 Promise.all #

  • 参数:接受一个数组,数组内都是Promise实例
  • 返回值:返回一个Promise实例,这个Promise实例的状态转移取决于参数的Promise实例的状态变化。当参数中所有的实例都处于resolve状态时,返回的Promise实例会变为resolve状态。如果参数中任意一个实例处于reject状态,返回的Promise实例变为reject状态。
    Promise.all([p1, p2]).then(function (result) {
      console.log(result); // [ '2.txt', '2' ]
    });
    

    不管两个promise谁先完成,Promise.all 方法会按照数组里面的顺序将结果返回

7.2 Promise.race #

  • 参数:接受一个数组,数组内都是Promise实例
  • 返回值:返回一个Promise实例,这个Promise实例的状态转移取决于参数的Promise实例的状态变化。当参数中任何一个实例处于resolve状态时,返回的Promise实例会变为resolve状态。如果参数中任意一个实例处于reject状态,返回的Promise实例变为reject状态。
    Promise.race([p1, p2]).then(function (result) {
      console.log(result); // [ '2.txt', '2' ]
    });
    

    7.3 Promise.resolve #

    返回一个Promise实例,这个实例处于resolve状态。

根据传入的参数不同有不同的功能:

  • 值(对象、数组、字符串等):作为resolve传递出去的值
  • Promise实例:原封不动返回

7.4 Promise.reject #

返回一个Promise实例,这个实例处于reject状态。

  • 参数一般就是抛出的错误信息。

8. q #

Q是一个在Javascript中实现promise的模块

8.1 q的基本用法 #

var Q = require('q');
var fs = require('fs');
function read(filename) {
    var deferred = Q.defer();
    fs.readFile(filename,'utf8', function (err, data) {
        if(err){
            deferred.reject(err);
        }else{
            deferred.resolve(data);
        }
    });
    return deferred.promise;
}

read('1.txt1').then(function(data){
    console.log(data);
},function(error){
    console.error(error);
});

8.2 q的简单实现 #

module.exports = {
    defer(){
        var _success,_error;
        return {
            resolve(data){
                _success(data);
            },
            reject(err){
                _error(err);
            },
            promise:{
                then(success,error){
                    _success = success;
                    _error = error;
                }
            }
        }
    }
}

8.3 q的实现 #

var defer = function () {
    var pending = [], value;
    return {
        resolve: function (_value) {
            if (pending) {
                value = _value;
                for (var i = 0, ii = pending.length; i < ii; i++) {
                    var callback = pending[i];
                    callback(value);
                }
                pending = undefined;
            }
        },
        promise: {
            then: function (callback) {
                if (pending) {
                    pending.push(callback);
                } else {
                    callback(value);
                }
            }
        }
    };
};

9. bluebird #

实现 promise 标准的库是功能最全,速度最快的一个库

9.1 bluebird经典使用 #

var Promise = require('./bluebird');

var readFile = Promise.promisify(require("fs").readFile);
readFile("1.txt", "utf8").then(function(contents) {
    console.log(contents);
})

var fs = Promise.promisifyAll(require("fs"));

fs.readFileAsync("1.txt", "utf8").then(function (contents) {
    console.log(contents);
})

9.2 bluebird简单实现 #

module.exports = {
    promisify(fn){
        return function () {
            var args = Array.from(arguments);
            return new Promise(function (resolve, reject) {
                fn.apply(null, args.concat(function (err) {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(arguments[1])
                    }
                }));
            })
        }
    },
    promisifyAll(obj){
        for(var attr in obj){
            if(obj.hasOwnProperty(attr) && typeof obj[attr] =='function'){
                obj[attr+'Async'] = this.promisify(obj[attr]);
            }
        }
        return obj;
    }
}

bluebirdAPI

11. 动画 #

11.1 回调方式 #

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>move</title>
    <style>
        .square{
            width:40px;
            height:40px;
        }
        .square1{
            background-color: red;
        }
        .square2{
            background-color: yellow;
        }
        .square3{
            background-color: blue;
        }
    </style>
</head>
<body>
<div class="square square1" style="margin-left: 0"></div>
<div class="square square2" style="margin-left: 0"></div>
<div class="square square3" style="margin-left: 0"></div>
</body>
<script>
    var square1 = document.querySelector('.square1');
    var square2 = document.querySelector('.square2');
    var square3 = document.querySelector('.square3');
    function move(element,target,cb){
        setTimeout(function () {
            var marginLeft = parseInt(element.style.marginLeft, 10);
            if(marginLeft == target){
                cb();
            }else{
                element.style.marginLeft = ++marginLeft+'px';
                move(element,target,cb);
            }
        },13);
    }
    move(square1,100,function(){
        move(square2,100,function(){
            move(square3,100);
        });
    });
</script>
</html>

11.2 promise方式 #

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>move</title>
    <style>
        .square{
            width:40px;
            height:40px;
        }
        .square1{
            background-color: red;
        }
        .square2{
            background-color: yellow;
        }
        .square3{
            background-color: blue;
        }
    </style>
</head>
<body>
<div class="square square1" style="margin-left: 0"></div>
<div class="square square2" style="margin-left: 0"></div>
<div class="square square3" style="margin-left: 0"></div>
</body>
<script>
    var square1 = document.querySelector('.square1');
    var square2 = document.querySelector('.square2');
    var square3 = document.querySelector('.square3');
    function move(element,target,cb){
        setTimeout(function () {
            var marginLeft = parseInt(element.style.marginLeft, 10);
            if(marginLeft == target){
                cb();
            }else{
                element.style.marginLeft = ++marginLeft+'px';
                move(element,target,cb);
            }
        },13);
    }
    function animate(element,target){
        return new Promise(function(resolve,reject){
            move(element,target,resolve);
        });
    }
    animate(square1,100)
    .then(function(){
        return animate(square2,100);
    })
    .then(function(){
        return animate(square3,100);
    });
</script>
</html>

12. co+promise #

12.1 co初体验 #

var co = require('co');

co(function* () {
    var result = yield Promise.resolve(true);
    console.log(result);
    var result = yield Promise.resolve(false);
    console.log(result);
})

12.2 co读文件 #

var co = require('co');
var fs = require('fs');
function readFile(filename){
    return new Promise(function(resolve,reject){
        fs.readFile(filename,'utf8',function(err,data){
            if(err){
                reject(err);
            }else{
                resolve(data);
            }
        })
    });
}
co(function* () {
    var one = yield readFile('1.txt');
    console.log(one);
    var two = yield readFile(one);
    console.log(two);
})

12.3 co实现 #

“`javascript
module.exports = function(gen){
var it = gen();
next();
function next(data){
var result = it.next(data);
if(!result.done){
result.value.then(next);
}
}
}
···

最后再说两句

promise是个好东西。如果你还在使用传统的回调函数的话,我建议你迁移到promise上。这样你的代码会更简介,更优雅,可读性也更强。

有这样的观点:promise是不完美的。promise确实比使用回调函数好,但是,如果你有别的选择的话,这两种方式最好都不要用。

尽管相比回调函数有许多优点,promise仍然是难于理解的,并且使用起来很容易出错。新手和老鸟都会经常将promise用的乱七八糟。不过说真的,这不是他们的错,应该甩锅给promise。因为它和我们在同步环境的代码很像,但仅仅是像,是一个优雅的替代品。

在同步环境下,你无需学习这些令人费解的规则和一些新的API。你可以随意使用像return,catch,throw这样的关键字以及for循环。你不需要时刻在脑中保持两个相并列的编程思想。

13. 资源 #

~~~

未经允许不得转载:WEB前端开发 » Javascript中隐藏大BOSS-Promise的使用方法

赞 (0)