JavaScript Promise 告别异步乱嵌套

原文一. 🔗

// 我们假设step1, step2, step3都是ajax调用后端
// 需求是这样,step1、step2、step3必须按顺序执行
function step1(resolve, reject) {
    console.log('步骤1');
    resolve('步骤11');
}

function step2(resolve, reject) {
    console.log('步骤2');
    resolve('步骤22');
}

function step3(resolve, reject) {
    console.log('步骤3');
    resolve('步骤33');
}

new Promise(step1).then(function(val){
    console.log(val);
    return new Promise(step2);
}).then(function(val){
    console.log(val);
    return new Promise(step3);
}).then(function(val){
    console.log(val);
    return val;
});


原文二. 🔗

首先是返回普通变量的情况:

new Promise(function(res, rej) {
    console.log(Date.now() + " start setTimeout 1");
    setTimeout(res, 2000);
}).then(function() {
    console.log(Date.now() + " timeout 1 call back");
    return 1024;
}).then(function(arg) {
    console.log(Date.now() + " last onFulfilled return " + arg);    
});

以上代码执行结果为:

$ node promisTest.js
1450277122125 start setTimeout 1
1450277124129 timeout 1 call back
1450277124129 last onFulfilled return 1024

是onFulfilled函数返回一个Promise变量可以使我们很方便的连续调用多个异步过程。比如我们可以这样来尝试连续做两个延时操作:

new Promise(function(res, rej) {
    console.log(Date.now() + " start setTimeout 1");
    setTimeout(res, 2000);
}).then(function() {
    console.log(Date.now() + " timeout 1 call back");
    return new Promise(function(res, rej) {
        console.log(Date.now() + " start setTimeout 2");
        setTimeout(res, 3000);
    });
}).then(function() {
    console.log(Date.now() + " timeout 2 call back");
});

执行结果如下:

$ node promisTest.js
1450277510275 start setTimeout 1
1450277512276 timeout 1 call back
1450277512276 start setTimeout 2
1450277515327 timeout 2 call back

可以看到,多个延时的回调函数被有序的排列下来,并没有出现喜闻乐见的金字塔状结构。虽然代码里面调用的都是异步过程,但是看起来就像是全部由同步过程构成的一样。这就是Promise带给我们的好处。

如果你有把啰嗦的代码提炼成单独函数的好习惯,那就更加画美不看了:

function timeout1() {
    return new Promise(function(res, rej) {
        console.log(Date.now() + " start timeout1");
        setTimeout(res, 2000);
    });
}

function timeout2() {
    return new Promise(function(res, rej) {
        console.log(Date.now() + " start timeout2");
        setTimeout(res, 3000);
    });
}

function timeout3() {
    return new Promise(function(res, rej) {
        console.log(Date.now() + " start timeout3");
        setTimeout(res, 4000);
    });
}

function timeout4() {
    return new Promise(function(res, rej) {
        console.log(Date.now() + " start timeout4");
        setTimeout(res, 5000);
    });
}

timeout1()
    .then(timeout2)
    .then(timeout3)
    .then(timeout4)
    .then(function() {
        console.log(Date.now() + " timout4 callback");
    });
$ node promisTest.js
1450278983342 start timeout1
1450278985343 start timeout2
1450278988351 start timeout3
1450278992356 start timeout4
1450278997370 timout4 callback

接下来我们可以再继续研究一下onFulfilled函数传入入参的问题。

我们已经知道,如果上一个onFulfilled函数返回了一个普通的值,那么这个值为作为这个onFulfilled函数的入参;那么如果上一个onFulfilled返回了一个Promise变量,这个onFulfilled的入参又来自哪里?

答案是,这个onFulfilled函数的入参,是上一个Promise中调用resolve函数时传入的值。

我们来看看普通的异步接口中,成功情况的回调是什么样的,就拿nodejs的上的fs.readFile(file[, options], callback)来说,它的典型调用例子如下

fs.readFile('/etc/passwd', function (err, data) {
  if (err) throw err;
  console.log(data);
});

因为对于fs.readFile这个函数而言,无论成功还是失败,它都会调用callback这个回调函数,所以这个回调接受两个入参,即失败时的异常描述err和成功时的返回结果data。

那么假如我们用Promise来重构这个读取文件的例子,我们应该怎么写呢?

首先是封装fs.readFile函数:

function readFile(fileName) {
    return new Promise(function(resolve, reject) {
        fs.readFile(fileName, function (err, data) {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

其次是调用:

readFile('theFile.txt').then(
    function(data) {
        console.log(data);
    }, 
    function(err) {
        throw err;
    }    
);
总结

下面请允许我用一段代码对本文讲解到的要点进行总结:

function callp1() {
    console.log(Date.now() + " start callp1");
    return new Promise(function(res, rej) {
        setTimeout(res, 2000);
    });
}

function callp2() {
    console.log(Date.now() + " start callp2");
    return new Promise(function(res, rej) {
        setTimeout(function() {
            res({arg1: 4, arg2: "arg2 value"});
        }, 3000);
    });
}

function callp3(arg) {
    console.log(Date.now() + " start callp3 with arg = " + arg);
    return new Promise(function(res, rej) {
        setTimeout(function() {
            res("callp3");
        }, arg * 1000);
    });
}

callp1().then(function() {
    console.log(Date.now() + " callp1 return");
    return callp2();
}).then(function(ret) {
    console.log(Date.now() + " callp2 return with ret value = " + JSON.stringify(ret));
    return callp3(ret.arg1);
}).then(function(ret) {
    console.log(Date.now() + " callp3 return with ret value = " + ret);
});
$ node promisTest.js
1450191479575 start callp1
1450191481597 callp1 return
1450191481599 start callp2
1450191484605 callp2 return with ret value = {"arg1":4,"arg2":"arg2 value"}
1450191484605 start callp3 with arg = 4
1450191488610 callp3 return with ret value = callp3
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容