百度前端技术学院是一个为大学生创办的免费的前端技术实践、分享、交流平台。由百度校园招聘组、百度校园品牌部、百度前端技术部以及多个百度的前端团队联合创办。学院组织了一批百度在职工程师,精心编写了数十个实践编码任务,将技术知识点系统有机地串联在各个充满趣味与挑战的任务中,同学们通过实际地编码练习来掌握知识,再辅以互相评价、学习笔记等方式,加深对于学习内容的理解。在过去的三年中,百度前端技术学院累积吸引了上万名同学参加,并且有数十名同学在学习后,顺利加入了百度,成为了百度的前端工程师。

JS Promise

JS Promise

在JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现。

例如:

request.onreadystatechange = function () {
    if (request.readyState === 4) {
        if (request.status === 200) {
            return success(request.responseText);
        } else {
            return fail(request.status);
        }
    }
}

这样写的缺点:

  • 把回调函数success(request.responseText)和fail(request.status)写到一个AJAX操作里很正常,但是不好看,而且不利于代码复用。

更好的是链式写法

var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
    .ifFail(fail);

链式写法

  • 先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success函数或fail函数。
  • 这样(支持链式写法异步执行)的对象就是Promise

Promise有各种开源实现,在ES6中被统一规范,由浏览器直接支持。

简单的Promise例子:生成0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:

function test (resolve, reject) {
    var timeout = Math.random() * 2;
    log("set timeout to: " + timeout + " seconds.");
    setTimeout(function () {
        if (timeout < 1) {
            log("call resolve()...");
            resolve("200 OK");
        } else {
            log("call reject()..");
            reject("timeout in " + timeout + "seconds.");
        }
    }, timeout * 1000);
}

var p1 = new Promise(test);
var p2 = p1.then(function (result) {
    console.log("成功: " + result);
});
var p3 = p2.catch(function (reason) {
    console.log("失败: " + reason);
});
  • 变量p1是一个Promise对象,它负责执行test函数。
  • Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了
new Promise(function (resolve, reject) {
    log('start new Promise...');
    var timeOut = Math.random() * 2;
    log('set timeout to: ' + timeOut + ' seconds.');
    setTimeout(function () {
        if (timeOut < 1) {
            log('call resolve()...');
            resolve('200 OK');
        }
        else {
            log('call reject()...');
            reject('timeout in ' + timeOut + ' seconds.');
        }
    }, timeOut * 1000);
}).then(function (r) {
    log('Done: ' + r);
}).catch(function (reason) {
    log('Failed: ' + reason);
});

Promise 实现顺序执行一组任务

  • 下面job1, job2, job3都是Promise对象
    job1.then(job2).then(job3).catch(handleError);
    

例子1

'use strict';

var logging = document.getElementById('test-promise2-log');
while (logging.children.length > 1) {
    logging.removeChild(logging.children[logging.children.length - 1]);
}

function log(s) {
    var p = document.createElement('p');
    p.innerHTML = s;
    logging.appendChild(p);
}

// 0.5秒后返回input*input的计算结果:
function multiply(input) {
    return new Promise(function (resolve, reject) {
        log('calculating ' + input + ' x ' + input + '...');
        setTimeout(resolve, 500, input * input);
    });
}

// 0.5秒后返回input+input的计算结果:
function add(input) {
    return new Promise(function (resolve, reject) {
        log('calculating ' + input + ' + ' + input + '...');
        setTimeout(resolve, 500, input + input);
    });
}

var p = new Promise(function (resolve, reject) {
    log('start new Promise...');
    resolve(123);
});

p.then(multiply)
 .then(add)
 .then(multiply)
 .then(add)
 .then(function (result) {
    log('Got value: ' + result);
});

例子2 ajax

'use strict';

// ajax函数将返回Promise对象:
function ajax(method, url, data) {
    var request = new XMLHttpRequest();
    return new Promise(function (resolve, reject) {
        request.onreadystatechange = function () {
            if (request.readyState === 4) {
                if (request.status === 200) {
                    resolve(request.responseText);
                } else {
                    reject(request.status);
                }
            }
        };
        request.open(method, url);
        request.send(data);
    });
}

var log = document.getElementById('test-promise-ajax-result');
var p = ajax('GET', '/api/categories');
p.then(function (text) { // 如果AJAX成功,获得响应内容
    log.innerText = text;
}).catch(function (status) { // 如果AJAX失败,获得响应代码
    log.innerText = 'ERROR: ' + status;
});

Promise 执行并行异步任务

一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
    console.log(results); // 获得一个Array: ['P1', 'P2']
});

有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现:

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P1'
});

如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。

JS Promise 迷你书

什么是Promise

Promise三种状态

  • Fulfilled
  • Rejected
  • Pending
  • promise对象的状态,从Pending转换为Fulfilled或Rejected之后, 这个promise对象的状态就不会再发生任何变化。
  • 也就是说,Promise与Event等不同,在.then 后执行的函数可以肯定地说只会被调用一次。

编写Promise对象

  • new Promise(fn) 返回一个promise对象
  • 在fn 中指定异步等处理
    • 处理结果正常的话,调用resolve(处理结果值)
    • 处理结果错误的话,调用reject(Error对象)

编写Promise对象处理方法

  • 被resolve后的处理,可以在.then 方法中传入想要调用的函数。
  • 被reject后的处理,可以在.then 的第二个参数 或者是在 .catch 方法中设置想要调用的函数。

实战Promise

Promise.resolve
Promise.resolve(42)相当于

new Promise(function(resolve) {
    resolve(42);
});
  • 在这段代码中的 resolve(42); 会让这个promise对象立即进入确定(即resolved)状态,并将 42 传递给后面then里所指定的 onFulfilled 函数。
  • 方法 Promise.resolve(value); 的返回值也是一个promise对象,所以我们可以像下面那样接着对其返回值进行 .then 调用。

Promise#then 不仅仅是注册一个回调函数那么简单,它还会将回调函数的返回值进行变换,创建并返回一个promise对象。

  • 使用promise.then(onFulfilled, onRejected) 的话
    • 在 onFulfilled 中发生异常的话,在 onRejected 中是捕获不到这个异常的。
  • 在 promise.then(onFulfilled).catch(onRejected) 的情况下
    • then 中产生的异常能在 .catch 中捕获
  • .then 和 .catch 在本质上是没有区别的
    • 需要分场合使用
0条评论